Merge "Add the base microversions test part"
diff --git a/HACKING.rst b/HACKING.rst
index 9f7487d..0962f80 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -330,24 +330,25 @@
# The created server should be in the detailed list of all servers
...
-Tempest includes a ``check_uuid.py`` tool that will test for the existence
-and uniqueness of idempotent_id metadata for every test. By default the
-tool runs against the Tempest package by calling::
+Tempest-lib includes a ``check-uuid`` tool that will test for the existence
+and uniqueness of idempotent_id metadata for every test. If you have
+tempest-lib installed you run the tool against Tempest by calling from the
+tempest repo::
- python check_uuid.py
+ check-uuid
It can be invoked against any test suite by passing a package name::
- python check_uuid.py --package <package_name>
+ check-uuid --package <package_name>
Tests without an ``idempotent_id`` can be automatically fixed by running
the command with the ``--fix`` flag, which will modify the source package
by inserting randomly generated uuids for every test that does not have
one::
- python check_uuid.py --fix
+ check-uuid --fix
-The ``check_uuid.py`` tool is used as part of the tempest gate job
+The ``check-uuid`` tool is used as part of the tempest gate job
to ensure that all tests have an ``idempotent_id`` decorator.
Branchless Tempest Considerations
diff --git a/requirements.txt b/requirements.txt
index ffe6f26..17d063d 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,7 +2,7 @@
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
pbr>=1.6
-cliff>=1.14.0 # Apache-2.0
+cliff>=1.15.0 # Apache-2.0
anyjson>=0.3.3
httplib2>=0.7.5
jsonschema!=2.5.0,<3.0.0,>=2.0.0
diff --git a/run_tempest.sh b/run_tempest.sh
index 0f32045..a704684 100755
--- a/run_tempest.sh
+++ b/run_tempest.sh
@@ -104,9 +104,9 @@
fi
if [ $serial -eq 1 ]; then
- ${wrapper} testr run --subunit $testrargs | ${wrapper} subunit-2to1 | ${wrapper} tools/colorizer.py
+ ${wrapper} testr run --subunit $testrargs | ${wrapper} subunit-trace -n -f
else
- ${wrapper} testr run --parallel --subunit $testrargs | ${wrapper} subunit-2to1 | ${wrapper} tools/colorizer.py
+ ${wrapper} testr run --parallel --subunit $testrargs | ${wrapper} subunit-trace -n -f
fi
}
diff --git a/run_tests.sh b/run_tests.sh
index 9a158e4..908056f 100755
--- a/run_tests.sh
+++ b/run_tests.sh
@@ -90,9 +90,9 @@
fi
if [ $serial -eq 1 ]; then
- ${wrapper} testr run --subunit $testrargs | ${wrapper} subunit-2to1 | ${wrapper} tools/colorizer.py
+ ${wrapper} testr run --subunit $testrargs | ${wrapper} subunit-trace -n -f
else
- ${wrapper} testr run --parallel --subunit $testrargs | ${wrapper} subunit-2to1 | ${wrapper} tools/colorizer.py
+ ${wrapper} testr run --parallel --subunit $testrargs | ${wrapper} subunit-trace -n -f
fi
}
diff --git a/tempest/api/identity/admin/v3/test_groups.py b/tempest/api/identity/admin/v3/test_groups.py
index 82e4ec8..260ea54 100644
--- a/tempest/api/identity/admin/v3/test_groups.py
+++ b/tempest/api/identity/admin/v3/test_groups.py
@@ -42,6 +42,21 @@
self.assertEqual(new_name, new_group['name'])
self.assertEqual(new_desc, new_group['description'])
+ @test.idempotent_id('b66eb441-b08a-4a6d-81ab-fef71baeb26c')
+ def test_group_update_with_few_fields(self):
+ name = data_utils.rand_name('Group')
+ old_description = data_utils.rand_name('Description')
+ group = self.groups_client.create_group(
+ name=name, description=old_description)['group']
+ self.addCleanup(self.groups_client.delete_group, group['id'])
+
+ new_name = data_utils.rand_name('UpdateGroup')
+ updated_group = self.groups_client.update_group(
+ group['id'], name=new_name)['group']
+ self.assertEqual(new_name, updated_group['name'])
+ # Verify that 'description' is not being updated or deleted.
+ self.assertEqual(old_description, updated_group['description'])
+
@test.attr(type='smoke')
@test.idempotent_id('1598521a-2f36-4606-8df9-30772bd51339')
def test_group_users_add_list_delete(self):
diff --git a/tempest/api/identity/admin/v3/test_users_negative.py b/tempest/api/identity/admin/v3/test_users_negative.py
index ca2aaa4..d40a5b9 100644
--- a/tempest/api/identity/admin/v3/test_users_negative.py
+++ b/tempest/api/identity/admin/v3/test_users_negative.py
@@ -33,3 +33,14 @@
u_name, u_password,
email=u_email,
domain_id=data_utils.rand_uuid_hex())
+
+ @test.attr(type=['negative'])
+ @test.idempotent_id('b3c9fccc-4134-46f5-b600-1da6fb0a3b1f')
+ def test_authentication_for_disabled_user(self):
+ # Attempt to authenticate for disabled user should fail
+ self.data.setup_test_v3_user()
+ self.disable_user(self.data.test_user)
+ self.assertRaises(lib_exc.Unauthorized, self.token.auth,
+ username=self.data.test_user,
+ password=self.data.test_password,
+ user_domain_id='default')
diff --git a/tempest/api/identity/base.py b/tempest/api/identity/base.py
index c56f4fb..0364f3a 100644
--- a/tempest/api/identity/base.py
+++ b/tempest/api/identity/base.py
@@ -170,6 +170,11 @@
if len(role) > 0:
return role[0]
+ @classmethod
+ def disable_user(cls, user_name):
+ user = cls.get_user_by_name(user_name)
+ cls.client.update_user(user['id'], user_name, enabled=False)
+
def delete_domain(self, domain_id):
# NOTE(mpavlase) It is necessary to disable the domain before deleting
# otherwise it raises Forbidden exception
diff --git a/tempest/api/image/v2/test_images_member.py b/tempest/api/image/v2/test_images_member.py
index d68ceff..98ff64b 100644
--- a/tempest/api/image/v2/test_images_member.py
+++ b/tempest/api/image/v2/test_images_member.py
@@ -20,14 +20,14 @@
def test_image_share_accept(self):
image_id = self._create_image()
member = self.os_img_client.add_image_member(image_id,
- self.alt_tenant_id)
+ member=self.alt_tenant_id)
self.assertEqual(member['member_id'], self.alt_tenant_id)
self.assertEqual(member['image_id'], image_id)
self.assertEqual(member['status'], 'pending')
self.assertNotIn(image_id, self._list_image_ids_as_alt())
self.alt_img_client.update_image_member(image_id,
self.alt_tenant_id,
- {'status': 'accepted'})
+ status='accepted')
self.assertIn(image_id, self._list_image_ids_as_alt())
body = self.os_img_client.list_image_members(image_id)
members = body['members']
@@ -41,24 +41,24 @@
def test_image_share_reject(self):
image_id = self._create_image()
member = self.os_img_client.add_image_member(image_id,
- self.alt_tenant_id)
+ member=self.alt_tenant_id)
self.assertEqual(member['member_id'], self.alt_tenant_id)
self.assertEqual(member['image_id'], image_id)
self.assertEqual(member['status'], 'pending')
self.assertNotIn(image_id, self._list_image_ids_as_alt())
self.alt_img_client.update_image_member(image_id,
self.alt_tenant_id,
- {'status': 'rejected'})
+ status='rejected')
self.assertNotIn(image_id, self._list_image_ids_as_alt())
@test.idempotent_id('a6ee18b9-4378-465e-9ad9-9a6de58a3287')
def test_get_image_member(self):
image_id = self._create_image()
self.os_img_client.add_image_member(image_id,
- self.alt_tenant_id)
+ member=self.alt_tenant_id)
self.alt_img_client.update_image_member(image_id,
self.alt_tenant_id,
- {'status': 'accepted'})
+ status='accepted')
self.assertIn(image_id, self._list_image_ids_as_alt())
member = self.os_img_client.show_image_member(image_id,
@@ -71,10 +71,10 @@
def test_remove_image_member(self):
image_id = self._create_image()
self.os_img_client.add_image_member(image_id,
- self.alt_tenant_id)
+ member=self.alt_tenant_id)
self.alt_img_client.update_image_member(image_id,
self.alt_tenant_id,
- {'status': 'accepted'})
+ status='accepted')
self.assertIn(image_id, self._list_image_ids_as_alt())
self.os_img_client.delete_image_member(image_id, self.alt_tenant_id)
@@ -94,14 +94,14 @@
def test_get_private_image(self):
image_id = self._create_image()
member = self.os_img_client.add_image_member(image_id,
- self.alt_tenant_id)
+ member=self.alt_tenant_id)
self.assertEqual(member['member_id'], self.alt_tenant_id)
self.assertEqual(member['image_id'], image_id)
self.assertEqual(member['status'], 'pending')
self.assertNotIn(image_id, self._list_image_ids_as_alt())
self.alt_img_client.update_image_member(image_id,
self.alt_tenant_id,
- {'status': 'accepted'})
+ status='accepted')
self.assertIn(image_id, self._list_image_ids_as_alt())
self.os_img_client.delete_image_member(image_id, self.alt_tenant_id)
self.assertNotIn(image_id, self._list_image_ids_as_alt())
diff --git a/tempest/api/image/v2/test_images_member_negative.py b/tempest/api/image/v2/test_images_member_negative.py
index ae8913c..d5ea291 100644
--- a/tempest/api/image/v2/test_images_member_negative.py
+++ b/tempest/api/image/v2/test_images_member_negative.py
@@ -23,22 +23,22 @@
def test_image_share_invalid_status(self):
image_id = self._create_image()
member = self.os_img_client.add_image_member(image_id,
- self.alt_tenant_id)
+ member=self.alt_tenant_id)
self.assertEqual(member['status'], 'pending')
self.assertRaises(lib_exc.BadRequest,
self.alt_img_client.update_image_member,
image_id, self.alt_tenant_id,
- {'status': 'notavalidstatus'})
+ status='notavalidstatus')
@test.attr(type=['negative'])
@test.idempotent_id('27002f74-109e-4a37-acd0-f91cd4597967')
def test_image_share_owner_cannot_accept(self):
image_id = self._create_image()
member = self.os_img_client.add_image_member(image_id,
- self.alt_tenant_id)
+ member=self.alt_tenant_id)
self.assertEqual(member['status'], 'pending')
self.assertNotIn(image_id, self._list_image_ids_as_alt())
self.assertRaises(lib_exc.Forbidden,
self.os_img_client.update_image_member,
- image_id, self.alt_tenant_id, {'status': 'accepted'})
+ image_id, self.alt_tenant_id, status='accepted')
self.assertNotIn(image_id, self._list_image_ids_as_alt())
diff --git a/tempest/api/image/v2/test_images_metadefs_namespaces.py b/tempest/api/image/v2/test_images_metadefs_namespaces.py
index d089c84..efb7b8b 100644
--- a/tempest/api/image/v2/test_images_metadefs_namespaces.py
+++ b/tempest/api/image/v2/test_images_metadefs_namespaces.py
@@ -29,42 +29,42 @@
resource_name = body['resource_types'][0]['name']
name = [{'name': resource_name}]
namespace_name = data_utils.rand_name('namespace')
- # create the metadef namespaces
- body = self.client.create_namespaces(namespace=namespace_name,
- visibility='public',
- description='Tempest',
- display_name=namespace_name,
- resource_type_associations=name,
- protected=True)
+ # create the metadef namespace
+ body = self.client.create_namespace(namespace=namespace_name,
+ visibility='public',
+ description='Tempest',
+ display_name=namespace_name,
+ resource_type_associations=name,
+ protected=True)
self.addCleanup(self._cleanup_namespace, namespace_name)
- # get namespaces details
- body = self.client.show_namespaces(namespace_name)
+ # get namespace details
+ body = self.client.show_namespace(namespace_name)
self.assertEqual(namespace_name, body['namespace'])
self.assertEqual('public', body['visibility'])
# unable to delete protected namespace
- self.assertRaises(lib_exc.Forbidden, self.client.delete_namespaces,
+ self.assertRaises(lib_exc.Forbidden, self.client.delete_namespace,
namespace_name)
# update the visibility to private and protected to False
- body = self.client.update_namespaces(namespace=namespace_name,
- description='Tempest',
- visibility='private',
- display_name=namespace_name,
- protected=False)
+ body = self.client.update_namespace(namespace=namespace_name,
+ description='Tempest',
+ visibility='private',
+ display_name=namespace_name,
+ protected=False)
self.assertEqual('private', body['visibility'])
self.assertEqual(False, body['protected'])
# now able to delete the non-protected namespace
- self.client.delete_namespaces(namespace_name)
+ self.client.delete_namespace(namespace_name)
def _cleanup_namespace(self, namespace_name):
# this is used to cleanup the resources
try:
- body = self.client.show_namespaces(namespace_name)
+ body = self.client.show_namespace(namespace_name)
self.assertEqual(namespace_name, body['namespace'])
- body = self.client.update_namespaces(namespace=namespace_name,
- description='Tempest',
- visibility='private',
- display_name=namespace_name,
- protected=False)
- self.client.delete_namespaces(namespace_name)
+ body = self.client.update_namespace(namespace=namespace_name,
+ description='Tempest',
+ visibility='private',
+ display_name=namespace_name,
+ protected=False)
+ self.client.delete_namespace(namespace_name)
except lib_exc.NotFound:
pass
diff --git a/tempest/api/network/base.py b/tempest/api/network/base.py
index b798cb2..b4ea29b 100644
--- a/tempest/api/network/base.py
+++ b/tempest/api/network/base.py
@@ -105,7 +105,7 @@
# Clean up metering labels
for metering_label in cls.metering_labels:
cls._try_delete_resource(
- cls.admin_client.delete_metering_label,
+ cls.admin_metering_labels_client.delete_metering_label,
metering_label['id'])
# Clean up ports
for port in cls.ports:
@@ -272,11 +272,12 @@
cls.admin_subnets_client = cls.os_adm.subnets_client
cls.admin_ports_client = cls.os_adm.ports_client
cls.admin_floating_ips_client = cls.os_adm.floating_ips_client
+ cls.admin_metering_labels_client = cls.os_adm.metering_labels_client
@classmethod
def create_metering_label(cls, name, description):
"""Wrapper utility that returns a test metering label."""
- body = cls.admin_client.create_metering_label(
+ body = cls.admin_metering_labels_client.create_metering_label(
description=description,
name=data_utils.rand_name("metering-label"))
metering_label = body['metering_label']
diff --git a/tempest/api/network/test_metering_extensions.py b/tempest/api/network/test_metering_extensions.py
index a213f92..c4021b4 100644
--- a/tempest/api/network/test_metering_extensions.py
+++ b/tempest/api/network/test_metering_extensions.py
@@ -51,9 +51,11 @@
def _delete_metering_label(self, metering_label_id):
# Deletes a label and verifies if it is deleted or not
- self.admin_client.delete_metering_label(metering_label_id)
+ self.admin_metering_labels_client.delete_metering_label(
+ metering_label_id)
# Asserting that the label is not found in list after deletion
- labels = self.admin_client.list_metering_labels(id=metering_label_id)
+ labels = self.admin_metering_labels_client.list_metering_labels(
+ id=metering_label_id)
self.assertEqual(len(labels['metering_labels']), 0)
def _delete_metering_label_rule(self, metering_label_rule_id):
@@ -68,7 +70,7 @@
@test.idempotent_id('e2fb2f8c-45bf-429a-9f17-171c70444612')
def test_list_metering_labels(self):
# Verify label filtering
- body = self.admin_client.list_metering_labels(id=33)
+ body = self.admin_metering_labels_client.list_metering_labels(id=33)
metering_labels = body['metering_labels']
self.assertEqual(0, len(metering_labels))
@@ -77,21 +79,22 @@
# Creates a label
name = data_utils.rand_name('metering-label-')
description = "label created by tempest"
- body = self.admin_client.create_metering_label(name=name,
- description=description)
+ body = self.admin_metering_labels_client.create_metering_label(
+ name=name, description=description)
metering_label = body['metering_label']
self.addCleanup(self._delete_metering_label,
metering_label['id'])
# Assert whether created labels are found in labels list or fail
# if created labels are not found in labels list
- labels = (self.admin_client.list_metering_labels(
+ labels = (self.admin_metering_labels_client.list_metering_labels(
id=metering_label['id']))
self.assertEqual(len(labels['metering_labels']), 1)
@test.idempotent_id('30abb445-0eea-472e-bd02-8649f54a5968')
def test_show_metering_label(self):
# Verifies the details of a label
- body = self.admin_client.show_metering_label(self.metering_label['id'])
+ body = self.admin_metering_labels_client.show_metering_label(
+ self.metering_label['id'])
metering_label = body['metering_label']
self.assertEqual(self.metering_label['id'], metering_label['id'])
self.assertEqual(self.metering_label['tenant_id'],
diff --git a/tempest/api_schema/response/compute/v2_1/agents.py b/tempest/api_schema/response/compute/v2_1/agents.py
deleted file mode 100644
index da38198..0000000
--- a/tempest/api_schema/response/compute/v2_1/agents.py
+++ /dev/null
@@ -1,60 +0,0 @@
-# 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.
-
-common_agent_info = {
- 'type': 'object',
- 'properties': {
- 'agent_id': {'type': ['integer', 'string']},
- 'hypervisor': {'type': 'string'},
- 'os': {'type': 'string'},
- 'architecture': {'type': 'string'},
- 'version': {'type': 'string'},
- 'url': {'type': 'string', 'format': 'uri'},
- 'md5hash': {'type': 'string'}
- },
- 'additionalProperties': False,
- 'required': ['agent_id', 'hypervisor', 'os', 'architecture',
- 'version', 'url', 'md5hash']
-}
-
-list_agents = {
- 'status_code': [200],
- 'response_body': {
- 'type': 'object',
- 'properties': {
- 'agents': {
- 'type': 'array',
- 'items': common_agent_info
- }
- },
- 'additionalProperties': False,
- 'required': ['agents']
- }
-}
-
-create_agent = {
- 'status_code': [200],
- 'response_body': {
- 'type': 'object',
- 'properties': {
- 'agent': common_agent_info
- },
- 'additionalProperties': False,
- 'required': ['agent']
- }
-}
-
-delete_agent = {
- 'status_code': [200]
-}
diff --git a/tempest/api_schema/response/compute/v2_1/tenant_networks.py b/tempest/api_schema/response/compute/v2_1/tenant_networks.py
deleted file mode 100644
index ddfab96..0000000
--- a/tempest/api_schema/response/compute/v2_1/tenant_networks.py
+++ /dev/null
@@ -1,53 +0,0 @@
-# Copyright 2015 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.
-
-param_network = {
- 'type': 'object',
- 'properties': {
- 'id': {'type': 'string'},
- 'cidr': {'type': ['string', 'null']},
- 'label': {'type': 'string'}
- },
- 'additionalProperties': False,
- 'required': ['id', 'cidr', 'label']
-}
-
-
-list_tenant_networks = {
- 'status_code': [200],
- 'response_body': {
- 'type': 'object',
- 'properties': {
- 'networks': {
- 'type': 'array',
- 'items': param_network
- }
- },
- 'additionalProperties': False,
- 'required': ['networks']
- }
-}
-
-
-get_tenant_network = {
- 'status_code': [200],
- 'response_body': {
- 'type': 'object',
- 'properties': {
- 'network': param_network
- },
- 'additionalProperties': False,
- 'required': ['network']
- }
-}
diff --git a/tempest/api_schema/response/compute/v2_1/tenant_usages.py b/tempest/api_schema/response/compute/v2_1/tenant_usages.py
deleted file mode 100644
index d51ef12..0000000
--- a/tempest/api_schema/response/compute/v2_1/tenant_usages.py
+++ /dev/null
@@ -1,92 +0,0 @@
-# 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 copy
-
-_server_usages = {
- 'type': 'array',
- 'items': {
- 'type': 'object',
- 'properties': {
- 'ended_at': {
- 'oneOf': [
- {'type': 'string'},
- {'type': 'null'}
- ]
- },
- 'flavor': {'type': 'string'},
- 'hours': {'type': 'number'},
- 'instance_id': {'type': 'string'},
- 'local_gb': {'type': 'integer'},
- 'memory_mb': {'type': 'integer'},
- 'name': {'type': 'string'},
- 'started_at': {'type': 'string'},
- 'state': {'type': 'string'},
- 'tenant_id': {'type': 'string'},
- 'uptime': {'type': 'integer'},
- 'vcpus': {'type': 'integer'},
- },
- 'required': ['ended_at', 'flavor', 'hours', 'instance_id', 'local_gb',
- 'memory_mb', 'name', 'started_at', 'state', 'tenant_id',
- 'uptime', 'vcpus']
- }
-}
-
-_tenant_usage_list = {
- 'type': 'object',
- 'properties': {
- 'server_usages': _server_usages,
- 'start': {'type': 'string'},
- 'stop': {'type': 'string'},
- 'tenant_id': {'type': 'string'},
- 'total_hours': {'type': 'number'},
- 'total_local_gb_usage': {'type': 'number'},
- 'total_memory_mb_usage': {'type': 'number'},
- 'total_vcpus_usage': {'type': 'number'},
- },
- 'required': ['start', 'stop', 'tenant_id',
- 'total_hours', 'total_local_gb_usage',
- 'total_memory_mb_usage', 'total_vcpus_usage']
-}
-
-# 'required' of get_tenant is different from list_tenant's.
-_tenant_usage_get = copy.deepcopy(_tenant_usage_list)
-_tenant_usage_get['required'] = ['server_usages', 'start', 'stop', 'tenant_id',
- 'total_hours', 'total_local_gb_usage',
- 'total_memory_mb_usage', 'total_vcpus_usage']
-
-list_tenant_usage = {
- 'status_code': [200],
- 'response_body': {
- 'type': 'object',
- 'properties': {
- 'tenant_usages': {
- 'type': 'array',
- 'items': _tenant_usage_list
- }
- },
- 'required': ['tenant_usages']
- }
-}
-
-get_tenant_usage = {
- 'status_code': [200],
- 'response_body': {
- 'type': 'object',
- 'properties': {
- 'tenant_usage': _tenant_usage_get
- },
- 'required': ['tenant_usage']
- }
-}
diff --git a/tempest/api_schema/response/compute/v2_1/versions.py b/tempest/api_schema/response/compute/v2_1/versions.py
deleted file mode 100644
index 08a9fab..0000000
--- a/tempest/api_schema/response/compute/v2_1/versions.py
+++ /dev/null
@@ -1,110 +0,0 @@
-# Copyright 2015 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 copy
-
-
-_version = {
- 'type': 'object',
- 'properties': {
- 'id': {'type': 'string'},
- 'links': {
- 'type': 'array',
- 'items': {
- 'type': 'object',
- 'properties': {
- 'href': {'type': 'string', 'format': 'uri'},
- 'rel': {'type': 'string'},
- 'type': {'type': 'string'},
- },
- 'required': ['href', 'rel'],
- 'additionalProperties': False
- }
- },
- 'status': {'type': 'string'},
- 'updated': {'type': 'string', 'format': 'date-time'},
- 'version': {'type': 'string'},
- 'min_version': {'type': 'string'},
- 'media-types': {
- 'type': 'array',
- 'properties': {
- 'base': {'type': 'string'},
- 'type': {'type': 'string'},
- }
- },
- },
- # NOTE: version and min_version have been added since Kilo,
- # so they should not be required.
- # NOTE(sdague): media-types only shows up in single version requests.
- 'required': ['id', 'links', 'status', 'updated'],
- 'additionalProperties': False
-}
-
-list_versions = {
- 'status_code': [200],
- 'response_body': {
- 'type': 'object',
- 'properties': {
- 'versions': {
- 'type': 'array',
- 'items': _version
- }
- },
- 'required': ['versions'],
- 'additionalProperties': False
- }
-}
-
-
-_detail_get_version = copy.deepcopy(_version)
-_detail_get_version['properties'].pop('min_version')
-_detail_get_version['properties'].pop('version')
-_detail_get_version['properties'].pop('updated')
-_detail_get_version['properties']['media-types'] = {
- 'type': 'array',
- 'items': {
- 'type': 'object',
- 'properties': {
- 'base': {'type': 'string'},
- 'type': {'type': 'string'}
- }
- }
-}
-_detail_get_version['required'] = ['id', 'links', 'status', 'media-types']
-
-get_version = {
- 'status_code': [300],
- 'response_body': {
- 'type': 'object',
- 'properties': {
- 'choices': {
- 'type': 'array',
- 'items': _detail_get_version
- }
- },
- 'required': ['choices'],
- 'additionalProperties': False
- }
-}
-
-get_one_version = {
- 'status_code': [200],
- 'response_body': {
- 'type': 'object',
- 'properties': {
- 'version': _version
- },
- 'additionalProperties': False
- }
-}
diff --git a/tempest/clients.py b/tempest/clients.py
index ded94b2..d16b47b 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -35,7 +35,8 @@
from tempest_lib.services.compute.hosts_client import HostsClient
from tempest_lib.services.compute.hypervisor_client import \
HypervisorClient
-from tempest_lib.services.compute.images_client import ImagesClient
+from tempest_lib.services.compute.images_client import ImagesClient \
+ as ComputeImagesClient
from tempest_lib.services.compute.instance_usage_audit_log_client import \
InstanceUsagesAuditLogClient
from tempest_lib.services.compute.limits_client import LimitsClient
@@ -52,6 +53,11 @@
from tempest_lib.services.compute.services_client import ServicesClient
from tempest_lib.services.compute.snapshots_client import \
SnapshotsClient as ComputeSnapshotsClient
+from tempest_lib.services.compute.tenant_networks_client import \
+ TenantNetworksClient
+from tempest_lib.services.compute.tenant_usages_client import \
+ TenantUsagesClient
+from tempest_lib.services.compute.versions_client import VersionsClient
from tempest_lib.services.identity.v2.token_client import TokenClient
from tempest_lib.services.identity.v3.token_client import V3TokenClient
@@ -72,11 +78,6 @@
from tempest.services.compute.json.server_groups_client import \
ServerGroupsClient
from tempest.services.compute.json.servers_client import ServersClient
-from tempest.services.compute.json.tenant_networks_client import \
- TenantNetworksClient
-from tempest.services.compute.json.tenant_usages_client import \
- TenantUsagesClient
-from tempest.services.compute.json.versions_client import VersionsClient
from tempest.services.compute.json.volumes_client import \
VolumesClient as ComputeVolumesClient
from tempest.services.data_processing.v1_1.data_processing_client import \
@@ -102,11 +103,13 @@
RegionClient as RegionV3Client
from tempest.services.identity.v3.json.service_client import \
ServiceClient as ServiceV3Client
-from tempest.services.image.v1.json.image_client import ImageClient
-from tempest.services.image.v2.json.image_client import ImageClientV2
+from tempest.services.image.v1.json.images_client import ImagesClient
+from tempest.services.image.v2.json.images_client import ImagesClientV2
from tempest.services.messaging.json.messaging_client import \
MessagingClient
from tempest.services.network.json.floating_ips_client import FloatingIPsClient
+from tempest.services.network.json.metering_labels_client import \
+ MeteringLabelsClient
from tempest.services.network.json.network_client import NetworkClient
from tempest.services.network.json.networks_client import NetworksClient
from tempest.services.network.json.ports_client import PortsClient
@@ -119,22 +122,22 @@
from tempest.services.telemetry.json.alarming_client import AlarmingClient
from tempest.services.telemetry.json.telemetry_client import \
TelemetryClient
-from tempest.services.volume.json.admin.volume_hosts_client import \
+from tempest.services.volume.v1.json.admin.volume_hosts_client import \
VolumeHostsClient
-from tempest.services.volume.json.admin.volume_quotas_client import \
+from tempest.services.volume.v1.json.admin.volume_quotas_client import \
VolumeQuotasClient
-from tempest.services.volume.json.admin.volume_services_client import \
+from tempest.services.volume.v1.json.admin.volume_services_client import \
VolumesServicesClient
-from tempest.services.volume.json.admin.volume_types_client import \
+from tempest.services.volume.v1.json.admin.volume_types_client import \
VolumeTypesClient
-from tempest.services.volume.json.availability_zone_client import \
+from tempest.services.volume.v1.json.availability_zone_client import \
VolumeAvailabilityZoneClient
-from tempest.services.volume.json.backups_client import BackupsClient
-from tempest.services.volume.json.extensions_client import \
+from tempest.services.volume.v1.json.backups_client import BackupsClient
+from tempest.services.volume.v1.json.extensions_client import \
ExtensionsClient as VolumeExtensionClient
-from tempest.services.volume.json.qos_client import QosSpecsClient
-from tempest.services.volume.json.snapshots_client import SnapshotsClient
-from tempest.services.volume.json.volumes_client import VolumesClient
+from tempest.services.volume.v1.json.qos_client import QosSpecsClient
+from tempest.services.volume.v1.json.snapshots_client import SnapshotsClient
+from tempest.services.volume.v1.json.volumes_client import VolumesClient
from tempest.services.volume.v2.json.admin.volume_hosts_client import \
VolumeHostsV2Client
from tempest.services.volume.v2.json.admin.volume_quotas_client import \
@@ -230,6 +233,14 @@
build_interval=CONF.network.build_interval,
build_timeout=CONF.network.build_timeout,
**self.default_params)
+ self.metering_labels_client = MeteringLabelsClient(
+ self.auth_provider,
+ CONF.network.catalog_type,
+ CONF.network.region or CONF.identity.region,
+ endpoint_type=CONF.network.endpoint_type,
+ build_interval=CONF.network.build_interval,
+ build_timeout=CONF.network.build_timeout,
+ **self.default_params)
self.messaging_client = MessagingClient(
self.auth_provider,
CONF.messaging.catalog_type,
@@ -250,7 +261,7 @@
endpoint_type=CONF.alarming.endpoint_type,
**self.default_params_with_timeout_values)
if CONF.service_available.glance:
- self.image_client = ImageClient(
+ self.image_client = ImagesClient(
self.auth_provider,
CONF.image.catalog_type,
CONF.image.region or CONF.identity.region,
@@ -258,7 +269,7 @@
build_interval=CONF.image.build_interval,
build_timeout=CONF.image.build_timeout,
**self.default_params)
- self.image_client_v2 = ImageClientV2(
+ self.image_client_v2 = ImagesClientV2(
self.auth_provider,
CONF.image.catalog_type,
CONF.image.region or CONF.identity.region,
@@ -319,7 +330,7 @@
self.server_groups_client = ServerGroupsClient(
self.auth_provider, **params)
self.limits_client = LimitsClient(self.auth_provider, **params)
- self.images_client = ImagesClient(self.auth_provider, **params)
+ self.images_client = ComputeImagesClient(self.auth_provider, **params)
self.keypairs_client = KeyPairsClient(self.auth_provider, **params)
self.quotas_client = QuotasClient(self.auth_provider, **params)
self.quota_classes_client = QuotaClassesClient(self.auth_provider,
diff --git a/tempest/cmd/cleanup_service.py b/tempest/cmd/cleanup_service.py
index ba6bf6c..f2dd7af 100644
--- a/tempest/cmd/cleanup_service.py
+++ b/tempest/cmd/cleanup_service.py
@@ -387,6 +387,7 @@
self.subnets_client = manager.subnets_client
self.ports_client = manager.ports_client
self.floating_ips_client = manager.floating_ips_client
+ self.metering_labels_client = manager.metering_labels_client
def _filter_by_conf_networks(self, item_list):
if not item_list or not all(('network_id' in i for i in item_list)):
@@ -600,7 +601,7 @@
class NetworkMeteringLabelService(NetworkService):
def list(self):
- client = self.client
+ client = self.metering_labels_client
labels = client.list_metering_labels()
labels = labels['metering_labels']
labels = self._filter_by_tenant_id(labels)
@@ -608,7 +609,7 @@
return labels
def delete(self):
- client = self.client
+ client = self.metering_labels_client
labels = self.list()
for label in labels:
try:
diff --git a/tempest/cmd/javelin.py b/tempest/cmd/javelin.py
index 184bb9a..c5e4a7e 100755
--- a/tempest/cmd/javelin.py
+++ b/tempest/cmd/javelin.py
@@ -128,14 +128,14 @@
from tempest.services.compute.json import security_group_rules_client
from tempest.services.compute.json import servers_client
from tempest.services.identity.v2.json import identity_client
-from tempest.services.image.v2.json import image_client
+from tempest.services.image.v2.json import images_client
from tempest.services.network.json import network_client
from tempest.services.network.json import subnets_client
from tempest.services.object_storage import container_client
from tempest.services.object_storage import object_client
from tempest.services.telemetry.json import alarming_client
from tempest.services.telemetry.json import telemetry_client
-from tempest.services.volume.json import volumes_client
+from tempest.services.volume.v1.json import volumes_client
CONF = config.CONF
OPTS = {}
@@ -213,7 +213,7 @@
**object_storage_params)
self.containers = container_client.ContainerClient(
_auth, **object_storage_params)
- self.images = image_client.ImageClientV2(
+ self.images = images_client.ImagesClientV2(
_auth,
CONF.image.catalog_type,
CONF.image.region or CONF.identity.region,
diff --git a/tempest/common/compute.py b/tempest/common/compute.py
index 6fc3843..5a14fbe 100644
--- a/tempest/common/compute.py
+++ b/tempest/common/compute.py
@@ -43,7 +43,7 @@
:param wait_until: Server status to wait for the server to reach after
its creation.
:param volume_backed: Whether the instance is volume backed or not.
- :returns a tuple
+ :returns: a tuple
"""
# TODO(jlanoux) add support of wait_until PINGABLE/SSHABLE
diff --git a/tempest/common/fixed_network.py b/tempest/common/fixed_network.py
index 1928a22..56cd331 100644
--- a/tempest/common/fixed_network.py
+++ b/tempest/common/fixed_network.py
@@ -83,7 +83,7 @@
is the network to be used, and it's not visible to the tenant
:param shared_network_name: name of the shared network to be used if no
tenant network is available in the creds provider
- :return a dict with 'id' and 'name' of the network
+ :returns: a dict with 'id' and 'name' of the network
"""
caller = misc_utils.find_test_caller()
net_creds = creds_provider.get_primary_creds()
diff --git a/tempest/common/glance_http.py b/tempest/common/glance_http.py
index e5431a0..800e977 100644
--- a/tempest/common/glance_http.py
+++ b/tempest/common/glance_http.py
@@ -24,12 +24,10 @@
import OpenSSL
from oslo_log import log as logging
-from oslo_serialization import jsonutils as json
import six
from six import moves
from six.moves import http_client as httplib
from six.moves.urllib import parse as urlparse
-from tempest_lib import exceptions as lib_exc
from tempest import exceptions as exc
@@ -51,19 +49,20 @@
self.endpoint_port = endpoint_parts.port
self.endpoint_path = endpoint_parts.path
- self.connection_class = self.get_connection_class(self.endpoint_scheme)
- self.connection_kwargs = self.get_connection_kwargs(
+ self.connection_class = self._get_connection_class(
+ self.endpoint_scheme)
+ self.connection_kwargs = self._get_connection_kwargs(
self.endpoint_scheme, **kwargs)
@staticmethod
- def get_connection_class(scheme):
+ def _get_connection_class(scheme):
if scheme == 'https':
return VerifiedHTTPSConnection
else:
return httplib.HTTPConnection
@staticmethod
- def get_connection_kwargs(scheme, **kwargs):
+ def _get_connection_kwargs(scheme, **kwargs):
_kwargs = {'timeout': float(kwargs.get('timeout', 600))}
if scheme == 'https':
@@ -75,7 +74,7 @@
return _kwargs
- def get_connection(self):
+ def _get_connection(self):
_class = self.connection_class
try:
return _class(self.endpoint_hostname, self.endpoint_port,
@@ -95,7 +94,7 @@
self._log_request(method, url, kwargs['headers'])
- conn = self.get_connection()
+ conn = self._get_connection()
try:
url_parts = urlparse.urlparse(url)
@@ -159,30 +158,6 @@
self.LOG.debug("Large body (%d) md5 summary: %s", length,
hashlib.md5(str_body).hexdigest())
- def json_request(self, method, url, **kwargs):
- kwargs.setdefault('headers', {})
- kwargs['headers'].setdefault('Content-Type', 'application/json')
- if kwargs['headers']['Content-Type'] != 'application/json':
- msg = "Only application/json content-type is supported."
- raise lib_exc.InvalidContentType(msg)
-
- if 'body' in kwargs:
- kwargs['body'] = json.dumps(kwargs['body'])
-
- resp, body_iter = self._http_request(url, method, **kwargs)
-
- if 'application/json' in resp.getheader('content-type', ''):
- body = ''.join([chunk for chunk in body_iter])
- try:
- body = json.loads(body)
- except ValueError:
- LOG.error('Could not decode response body as JSON')
- else:
- msg = "Only json/application content-type is supported."
- raise lib_exc.InvalidContentType(msg)
-
- return resp, body
-
def raw_request(self, method, url, **kwargs):
kwargs.setdefault('headers', {})
kwargs['headers'].setdefault('Content-Type',
diff --git a/tempest/common/utils/linux/remote_client.py b/tempest/common/utils/linux/remote_client.py
index 10654ff..025b79f 100644
--- a/tempest/common/utils/linux/remote_client.py
+++ b/tempest/common/utils/linux/remote_client.py
@@ -95,15 +95,24 @@
return self.exec_command(cmd)
def ping_host(self, host, count=CONF.compute.ping_count,
- size=CONF.compute.ping_size):
+ size=CONF.compute.ping_size, nic=None):
addr = netaddr.IPAddress(host)
cmd = 'ping6' if addr.version == 6 else 'ping'
+ if nic:
+ cmd = 'sudo {cmd} -I {nic}'.format(cmd=cmd, nic=nic)
cmd += ' -c{0} -w{0} -s{1} {2}'.format(count, size, host)
return self.exec_command(cmd)
- def get_mac_address(self):
- cmd = "ip addr | awk '/ether/ {print $2}'"
- return self.exec_command(cmd)
+ def set_mac_address(self, nic, address):
+ self.set_nic_state(nic=nic, state="down")
+ cmd = "sudo ip link set dev {0} address {1}".format(nic, address)
+ self.exec_command(cmd)
+ self.set_nic_state(nic=nic, state="up")
+
+ def get_mac_address(self, nic=""):
+ show_nic = "show {nic} ".format(nic=nic) if nic else ""
+ cmd = "ip addr %s| awk '/ether/ {print $2}'" % show_nic
+ return self.exec_command(cmd).strip().lower()
def get_nic_name(self, address):
cmd = "ip -o addr | awk '/%s/ {print $2}'" % address
@@ -121,8 +130,8 @@
)
return self.exec_command(cmd)
- def turn_nic_on(self, nic):
- cmd = "sudo ip link set {nic} up".format(nic=nic)
+ def set_nic_state(self, nic, state="up"):
+ cmd = "sudo ip link set {nic} {state}".format(nic=nic, state=state)
return self.exec_command(cmd)
def get_pids(self, pr_name):
diff --git a/tempest/hacking/ignored_list_T110.txt b/tempest/hacking/ignored_list_T110.txt
index f1655d0..211a7d6 100644
--- a/tempest/hacking/ignored_list_T110.txt
+++ b/tempest/hacking/ignored_list_T110.txt
@@ -7,6 +7,6 @@
./tempest/services/object_storage/object_client.py
./tempest/services/telemetry/json/alarming_client.py
./tempest/services/telemetry/json/telemetry_client.py
-./tempest/services/volume/json/qos_client.py
-./tempest/services/volume/json/backups_client.py
+./tempest/services/volume/base/base_qos_client.py
+./tempest/services/volume/base/base_backups_client.py
./tempest/services/baremetal/base.py
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index ed65c42..64f9e31 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -23,7 +23,7 @@
from tempest_lib.common.utils import misc as misc_utils
from tempest_lib import exceptions as lib_exc
-from tempest.common import fixed_network
+from tempest.common import compute
from tempest.common.utils import data_utils
from tempest.common.utils.linux import remote_client
from tempest.common import waiters
@@ -158,52 +158,100 @@
return body['keypair']
def create_server(self, name=None, image=None, flavor=None,
- wait_on_boot=True, wait_on_delete=True,
- create_kwargs=None):
- """Creates VM instance.
+ validatable=False, wait_until=None,
+ wait_on_delete=True, clients=None, **kwargs):
+ """Wrapper utility that returns a test server.
- @param image: image from which to create the instance
- @param wait_on_boot: wait for status ACTIVE before continue
- @param wait_on_delete: force synchronous delete on cleanup
- @param create_kwargs: additional details for instance creation
- @return: server dict
+ This wrapper utility calls the common create test server and
+ returns a test server. The purpose of this wrapper is to minimize
+ the impact on the code of the tests already using this
+ function.
"""
- if name is None:
- name = data_utils.rand_name(self.__class__.__name__)
- if image is None:
- image = CONF.compute.image_ref
- if flavor is None:
- flavor = CONF.compute.flavor_ref
- if create_kwargs is None:
- create_kwargs = {}
- network = self.get_tenant_network()
- create_kwargs = fixed_network.set_networks_kwarg(network,
- create_kwargs)
- LOG.debug("Creating a server (name: %s, image: %s, flavor: %s)",
- name, image, flavor)
- server = self.servers_client.create_server(name=name, imageRef=image,
- flavorRef=flavor,
- **create_kwargs)['server']
+ # NOTE(jlanoux): As a first step, ssh checks in the scenario
+ # tests need to be run regardless of the run_validation and
+ # validatable parameters and thus until the ssh validation job
+ # becomes voting in CI. The test resources management and IP
+ # association are taken care of in the scenario tests.
+ # Therefore, the validatable parameter is set to false in all
+ # those tests. In this way create_server just return a standard
+ # server and the scenario tests always perform ssh checks.
+
+ # Needed for the cross_tenant_traffic test:
+ if clients is None:
+ clients = self.manager
+
+ vnic_type = CONF.network.port_vnic_type
+
+ # If vnic_type is configured create port for
+ # every network
+ if vnic_type:
+ ports = []
+ networks = []
+ create_port_body = {'binding:vnic_type': vnic_type,
+ 'namestart': 'port-smoke'}
+ if kwargs:
+ # Convert security group names to security group ids
+ # to pass to create_port
+ if 'security_groups' in kwargs:
+ security_groups =\
+ clients.network_client.list_security_groups(
+ ).get('security_groups')
+ sec_dict = dict([(s['name'], s['id'])
+ for s in security_groups])
+
+ sec_groups_names = [s['name'] for s in kwargs.pop(
+ 'security_groups')]
+ security_groups_ids = [sec_dict[s]
+ for s in sec_groups_names]
+
+ if security_groups_ids:
+ create_port_body[
+ 'security_groups'] = security_groups_ids
+ networks = kwargs.pop('networks')
+
+ # If there are no networks passed to us we look up
+ # for the tenant's private networks and create a port
+ # if there is only one private network. The same behaviour
+ # as we would expect when passing the call to the clients
+ # with no networks
+ if not networks:
+ networks = clients.networks_client.list_networks(
+ filters={'router:external': False})
+ self.assertEqual(1, len(networks),
+ "There is more than one"
+ " network for the tenant")
+ for net in networks:
+ net_id = net['uuid']
+ port = self._create_port(network_id=net_id,
+ client=clients.ports_client,
+ **create_port_body)
+ ports.append({'port': port.id})
+ if ports:
+ kwargs['networks'] = ports
+ self.ports = ports
+
+ tenant_network = self.get_tenant_network()
+
+ body, servers = compute.create_test_server(
+ clients,
+ tenant_network=tenant_network,
+ wait_until=wait_until,
+ **kwargs)
+
+ # TODO(jlanoux) Move wait_on_delete in compute.py
if wait_on_delete:
self.addCleanup(waiters.wait_for_server_termination,
- self.servers_client,
- server['id'])
+ clients.servers_client,
+ body['id'])
+
self.addCleanup_with_wait(
waiter_callable=waiters.wait_for_server_termination,
- thing_id=server['id'], thing_id_param='server_id',
+ thing_id=body['id'], thing_id_param='server_id',
cleanup_callable=self.delete_wrapper,
- cleanup_args=[self.servers_client.delete_server, server['id']],
- waiter_client=self.servers_client)
- if wait_on_boot:
- waiters.wait_for_server_status(self.servers_client,
- server_id=server['id'],
- status='ACTIVE')
- # The instance retrieved on creation is missing network
- # details, necessitating retrieval after it becomes active to
- # ensure correct details.
- server = self.servers_client.show_server(server['id'])['server']
- self.assertEqual(server['name'], name)
+ cleanup_args=[clients.servers_client.delete_server, body['id']],
+ waiter_client=clients.servers_client)
+ server = clients.servers_client.show_server(body['id'])['server']
return server
def create_volume(self, size=None, name=None, snapshot_id=None,
@@ -321,7 +369,7 @@
username = CONF.scenario.ssh_user
# Set this with 'keypair' or others to log in with keypair or
# username/password.
- if CONF.compute.ssh_auth_method == 'keypair':
+ if CONF.validation.auth_method == 'keypair':
password = None
if private_key is None:
private_key = self.keypair['private_key']
@@ -491,7 +539,7 @@
def ping_ip_address(self, ip_address, should_succeed=True,
ping_timeout=None):
- timeout = ping_timeout or CONF.compute.ping_timeout
+ timeout = ping_timeout or CONF.validation.ping_timeout
cmd = ['ping', '-c1', '-w1', ip_address]
def ping():
@@ -696,8 +744,8 @@
def cidr_in_use(cidr, tenant_id):
"""Check cidr existence
- :return True if subnet with cidr already exist in tenant
- False else
+ :returns: True if subnet with cidr already exist in tenant
+ False else
"""
cidr_in_use = self._list_subnets(tenant_id=tenant_id, cidr=cidr)
return len(cidr_in_use) != 0
@@ -865,18 +913,20 @@
self._log_net_info(e)
raise
- def _check_remote_connectivity(self, source, dest, should_succeed=True):
+ def _check_remote_connectivity(self, source, dest, should_succeed=True,
+ nic=None):
"""check ping server via source ssh connection
:param source: RemoteClient: an ssh connection from which to ping
:param dest: and IP to ping against
:param should_succeed: boolean should ping succeed or not
+ :param nic: specific network interface to ping from
:returns: boolean -- should_succeed == ping
:returns: ping is false if ping failed
"""
def ping_remote():
try:
- source.ping_host(dest)
+ source.ping_host(dest, nic=nic)
except lib_exc.SSHExecCommandFailed:
LOG.warn('Failed to ping IP: %s via a ssh connection from: %s.'
% (dest, source.ssh_client.host))
@@ -884,7 +934,7 @@
return should_succeed
return tempest.test.call_until_true(ping_remote,
- CONF.compute.ping_timeout,
+ CONF.validation.ping_timeout,
1)
def _create_security_group(self, client=None, tenant_id=None,
@@ -1135,71 +1185,6 @@
subnet.add_to_router(router.id)
return network, subnet, router
- def create_server(self, name=None, image=None, flavor=None,
- wait_on_boot=True, wait_on_delete=True,
- network_client=None, networks_client=None,
- ports_client=None, create_kwargs=None):
- if network_client is None:
- network_client = self.network_client
- if networks_client is None:
- networks_client = self.networks_client
- if ports_client is None:
- ports_client = self.ports_client
-
- vnic_type = CONF.network.port_vnic_type
-
- # If vnic_type is configured create port for
- # every network
- if vnic_type:
- ports = []
- networks = []
- create_port_body = {'binding:vnic_type': vnic_type,
- 'namestart': 'port-smoke'}
- if create_kwargs:
- # Convert security group names to security group ids
- # to pass to create_port
- if create_kwargs.get('security_groups'):
- security_groups = network_client.list_security_groups(
- ).get('security_groups')
- sec_dict = dict([(s['name'], s['id'])
- for s in security_groups])
-
- sec_groups_names = [s['name'] for s in create_kwargs.get(
- 'security_groups')]
- security_groups_ids = [sec_dict[s]
- for s in sec_groups_names]
-
- if security_groups_ids:
- create_port_body[
- 'security_groups'] = security_groups_ids
- networks = create_kwargs.get('networks')
-
- # If there are no networks passed to us we look up
- # for the tenant's private networks and create a port
- # if there is only one private network. The same behaviour
- # as we would expect when passing the call to the clients
- # with no networks
- if not networks:
- networks = networks_client.list_networks(filters={
- 'router:external': False})
- self.assertEqual(1, len(networks),
- "There is more than one"
- " network for the tenant")
- for net in networks:
- net_id = net['uuid']
- port = self._create_port(network_id=net_id,
- client=ports_client,
- **create_port_body)
- ports.append({'port': port.id})
- if ports:
- create_kwargs['networks'] = ports
- self.ports = ports
-
- return super(NetworkScenarioTest, self).create_server(
- name=name, image=image, flavor=flavor,
- wait_on_boot=wait_on_boot, wait_on_delete=wait_on_delete,
- create_kwargs=create_kwargs)
-
# power/provision states as of icehouse
class BaremetalPowerStates(object):
@@ -1322,11 +1307,8 @@
dest.validate_authentication()
def boot_instance(self):
- create_kwargs = {
- 'key_name': self.keypair['name']
- }
self.instance = self.create_server(
- wait_on_boot=False, create_kwargs=create_kwargs)
+ key_name=self.keypair['name'])
self.wait_node(self.instance['id'])
self.node = self.get_node(instance_id=self.instance['id'])
diff --git a/tempest/scenario/test_baremetal_basic_ops.py b/tempest/scenario/test_baremetal_basic_ops.py
index e629f0a..9415629 100644
--- a/tempest/scenario/test_baremetal_basic_ops.py
+++ b/tempest/scenario/test_baremetal_basic_ops.py
@@ -113,7 +113,7 @@
self.boot_instance()
self.validate_ports()
self.verify_connectivity()
- if CONF.compute.ssh_connect_method == 'floating':
+ if CONF.validation.connect_method == 'floating':
floating_ip = self.create_floating_ip(self.instance)['ip']
self.verify_connectivity(ip=floating_ip)
diff --git a/tempest/scenario/test_encrypted_cinder_volumes.py b/tempest/scenario/test_encrypted_cinder_volumes.py
index 082a8bf..dcd77ad 100644
--- a/tempest/scenario/test_encrypted_cinder_volumes.py
+++ b/tempest/scenario/test_encrypted_cinder_volumes.py
@@ -45,8 +45,9 @@
image = self.glance_image_create()
keypair = self.create_keypair()
- return self.create_server(image=image,
- create_kwargs={'key_name': keypair['name']})
+ return self.create_server(image_id=image,
+ key_name=keypair['name'],
+ wait_until='ACTIVE')
def create_encrypted_volume(self, encryption_provider, volume_type):
volume_type = self.create_volume_type(name=volume_type)
diff --git a/tempest/scenario/test_minimum_basic.py b/tempest/scenario/test_minimum_basic.py
index 92e3592..25e3e6f 100644
--- a/tempest/scenario/test_minimum_basic.py
+++ b/tempest/scenario/test_minimum_basic.py
@@ -117,9 +117,9 @@
image = self.glance_image_create()
keypair = self.create_keypair()
- create_kwargs = {'key_name': keypair['name']}
- server = self.create_server(image=image,
- create_kwargs=create_kwargs)
+ server = self.create_server(image_id=image,
+ key_name=keypair['name'],
+ wait_until='ACTIVE')
servers = self.nova_list()
self.assertIn(server['id'], [x['id'] for x in servers])
diff --git a/tempest/scenario/test_network_advanced_server_ops.py b/tempest/scenario/test_network_advanced_server_ops.py
index 3689508..a45a730 100644
--- a/tempest/scenario/test_network_advanced_server_ops.py
+++ b/tempest/scenario/test_network_advanced_server_ops.py
@@ -57,16 +57,13 @@
security_group = self._create_security_group()
network, subnet, router = self.create_networks()
public_network_id = CONF.network.public_network_id
- create_kwargs = {
- 'networks': [
- {'uuid': network.id},
- ],
- 'key_name': keypair['name'],
- 'security_groups': [{'name': security_group['name']}],
- }
server_name = data_utils.rand_name('server-smoke')
- server = self.create_server(name=server_name,
- create_kwargs=create_kwargs)
+ server = self.create_server(
+ name=server_name,
+ networks=[{'uuid': network.id}],
+ key_name=keypair['name'],
+ security_groups=[{'name': security_group['name']}],
+ wait_until='ACTIVE')
floating_ip = self.create_floating_ip(server, public_network_id)
# Verify that we can indeed connect to the server before we mess with
# it's state
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index 8fefd9e..41d13fe 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -155,16 +155,16 @@
keypair = self.create_keypair()
self.keypairs[keypair['name']] = keypair
security_groups = [{'name': self.security_group['name']}]
- create_kwargs = {
- 'networks': [
- {'uuid': network.id},
- ],
- 'key_name': keypair['name'],
- 'security_groups': security_groups,
- }
+ network = {'uuid': network.id}
if port_id is not None:
- create_kwargs['networks'][0]['port'] = port_id
- server = self.create_server(name=name, create_kwargs=create_kwargs)
+ network['port'] = port_id
+
+ server = self.create_server(
+ name=name,
+ networks=[network],
+ key_name=keypair['name'],
+ security_groups=security_groups,
+ wait_until='ACTIVE')
self.servers.append(server)
return server
@@ -287,7 +287,7 @@
num, new_nic = self.diff_list[0]
ssh_client.assign_static_ip(nic=new_nic,
addr=new_port.fixed_ips[0]['ip_address'])
- ssh_client.turn_nic_on(nic=new_nic)
+ ssh_client.set_nic_state(nic=new_nic)
def _get_server_nics(self, ssh_client):
reg = re.compile(r'(?P<num>\d+): (?P<nic_name>\w+):')
@@ -737,3 +737,49 @@
self.check_public_network_connectivity(
should_connect=True,
msg='After router rescheduling')
+
+ @test.requires_ext(service='network', extension='port-security')
+ @test.idempotent_id('7c0bb1a2-d053-49a4-98f9-ca1a1d849f63')
+ @test.services('compute', 'network')
+ def test_port_security_macspoofing_port(self):
+ """Tests port_security extension enforces mac spoofing
+
+ 1. create a new network
+ 2. connect VM to new network
+ 4. check VM can ping new network DHCP port
+ 5. spoof mac on new new network interface
+ 6. check Neutron enforces mac spoofing and blocks pings via spoofed
+ interface
+ 7. disable port-security on the spoofed port
+ 8. check Neutron allows pings via spoofed interface
+ """
+ spoof_mac = "00:00:00:00:00:01"
+
+ # Create server
+ self._setup_network_and_servers()
+ self.check_public_network_connectivity(should_connect=False)
+ self._create_new_network()
+ self._hotplug_server()
+ fip, server = self.floating_ip_tuple
+ new_ports = self._list_ports(device_id=server["id"],
+ network_id=self.new_net["id"])
+ spoof_port = new_ports[0]
+ private_key = self._get_server_key(server)
+ ssh_client = self.get_remote_client(fip.floating_ip_address,
+ private_key=private_key)
+ spoof_nic = ssh_client.get_nic_name(spoof_port["mac_address"])
+ dhcp_ports = self._list_ports(device_owner="network:dhcp",
+ network_id=self.new_net["id"])
+ new_net_dhcp = dhcp_ports[0]["fixed_ips"][0]["ip_address"]
+ self._check_remote_connectivity(ssh_client, dest=new_net_dhcp,
+ nic=spoof_nic, should_succeed=True)
+ ssh_client.set_mac_address(spoof_nic, spoof_mac)
+ new_mac = ssh_client.get_mac_address(nic=spoof_nic)
+ self.assertEqual(spoof_mac, new_mac)
+ self._check_remote_connectivity(ssh_client, dest=new_net_dhcp,
+ nic=spoof_nic, should_succeed=False)
+ self.ports_client.update_port(spoof_port["id"],
+ port_security_enabled=False,
+ security_groups=[])
+ self._check_remote_connectivity(ssh_client, dest=new_net_dhcp,
+ nic=spoof_nic, should_succeed=True)
diff --git a/tempest/scenario/test_network_v6.py b/tempest/scenario/test_network_v6.py
index 151eef8..d6ad46a 100644
--- a/tempest/scenario/test_network_v6.py
+++ b/tempest/scenario/test_network_v6.py
@@ -65,9 +65,6 @@
super(TestGettingAddress, self).setUp()
self.keypair = self.create_keypair()
self.sec_grp = self._create_security_group(tenant_id=self.tenant_id)
- self.srv_kwargs = {
- 'key_name': self.keypair['name'],
- 'security_groups': [{'name': self.sec_grp['name']}]}
def prepare_network(self, address6_mode, n_subnets6=1, dualnet=False):
"""Prepare network
@@ -119,11 +116,13 @@
def prepare_server(self, networks=None):
username = CONF.compute.image_ssh_user
- create_kwargs = self.srv_kwargs
networks = networks or [self.network]
- create_kwargs['networks'] = [{'uuid': n.id} for n in networks]
- srv = self.create_server(create_kwargs=create_kwargs)
+ srv = self.create_server(
+ key_name=self.keypair['name'],
+ security_groups=[{'name': self.sec_grp['name']}],
+ networks=[{'uuid': n.id} for n in networks],
+ wait_until='ACTIVE')
fip = self.create_floating_ip(thing=srv)
ips = self.define_server_ips(srv=srv)
ssh = self.get_remote_client(
@@ -148,7 +147,7 @@
"ports: %s")
% (self.network_v6, ports))
mac6 = ports[0]
- ssh.turn_nic_on(ssh.get_nic_name(mac6))
+ ssh.set_nic_state(ssh.get_nic_name(mac6))
def _prepare_and_test(self, address6_mode, n_subnets6=1, dualnet=False):
net_list = self.prepare_network(address6_mode=address6_mode,
@@ -181,10 +180,10 @@
guest_has_address, sshv4_2, ips_from_api_2['6'][i])
self.assertTrue(test.call_until_true(srv1_v6_addr_assigned,
- CONF.compute.ping_timeout, 1))
+ CONF.validation.ping_timeout, 1))
self.assertTrue(test.call_until_true(srv2_v6_addr_assigned,
- CONF.compute.ping_timeout, 1))
+ CONF.validation.ping_timeout, 1))
self._check_connectivity(sshv4_1, ips_from_api_2['4'])
self._check_connectivity(sshv4_2, ips_from_api_1['4'])
diff --git a/tempest/scenario/test_security_groups_basic_ops.py b/tempest/scenario/test_security_groups_basic_ops.py
index 6a23c4b..e266dc2 100644
--- a/tempest/scenario/test_security_groups_basic_ops.py
+++ b/tempest/scenario/test_security_groups_basic_ops.py
@@ -234,23 +234,16 @@
def _create_server(self, name, tenant, security_groups=None):
"""creates a server and assigns to security group"""
- self._set_compute_context(tenant)
if security_groups is None:
security_groups = [tenant.security_groups['default']]
security_groups_names = [{'name': s['name']} for s in security_groups]
- create_kwargs = {
- 'networks': [
- {'uuid': tenant.network.id},
- ],
- 'key_name': tenant.keypair['name'],
- 'security_groups': security_groups_names
- }
server = self.create_server(
name=name,
- network_client=tenant.manager.network_client,
- networks_client=tenant.manager.networks_client,
- ports_client=tenant.manager.ports_client,
- create_kwargs=create_kwargs)
+ networks=[{'uuid': tenant.network.id}],
+ key_name=tenant.keypair['name'],
+ security_groups=security_groups_names,
+ wait_until='ACTIVE',
+ clients=tenant.manager)
self.assertEqual(
sorted([s['name'] for s in security_groups]),
sorted([s['name'] for s in server['security_groups']]))
@@ -293,10 +286,6 @@
subnets_client=tenant.manager.subnets_client)
tenant.set_network(network, subnet, router)
- def _set_compute_context(self, tenant):
- self.servers_client = tenant.manager.servers_client
- return self.servers_client
-
def _deploy_tenant(self, tenant_or_id):
"""creates:
@@ -310,7 +299,6 @@
tenant = self.tenants[tenant_or_id]
else:
tenant = tenant_or_id
- self._set_compute_context(tenant)
self._create_tenant_keypairs(tenant)
self._create_tenant_network(tenant)
self._create_tenant_security_groups(tenant)
diff --git a/tempest/scenario/test_server_advanced_ops.py b/tempest/scenario/test_server_advanced_ops.py
index 9387dc7..4b932ce 100644
--- a/tempest/scenario/test_server_advanced_ops.py
+++ b/tempest/scenario/test_server_advanced_ops.py
@@ -53,7 +53,7 @@
@test.services('compute')
def test_resize_server_confirm(self):
# We create an instance for use in this test
- instance = self.create_server()
+ instance = self.create_server(wait_until='ACTIVE')
instance_id = instance['id']
resize_flavor = CONF.compute.flavor_ref_alt
LOG.debug("Resizing instance %s from flavor %s to flavor %s",
@@ -74,7 +74,7 @@
@test.services('compute')
def test_server_sequence_suspend_resume(self):
# We create an instance for use in this test
- instance = self.create_server()
+ instance = self.create_server(wait_until='ACTIVE')
instance_id = instance['id']
LOG.debug("Suspending instance %s. Current status: %s",
instance_id, instance['status'])
diff --git a/tempest/scenario/test_server_basic_ops.py b/tempest/scenario/test_server_basic_ops.py
index 8a19254..239e120 100644
--- a/tempest/scenario/test_server_basic_ops.py
+++ b/tempest/scenario/test_server_basic_ops.py
@@ -72,20 +72,6 @@
def add_keypair(self):
self.keypair = self.create_keypair()
- def boot_instance(self):
- # Create server with image and flavor from input scenario
- security_groups = [{'name': self.security_group['name']}]
- self.md = {'meta1': 'data1', 'meta2': 'data2', 'metaN': 'dataN'}
- create_kwargs = {
- 'key_name': self.keypair['name'],
- 'security_groups': security_groups,
- 'config_drive': CONF.compute_feature_enabled.config_drive,
- 'metadata': self.md
- }
- self.instance = self.create_server(image=self.image_ref,
- flavor=self.flavor_ref,
- create_kwargs=create_kwargs)
-
def verify_ssh(self):
if self.run_ssh:
# Obtain a floating IP
@@ -139,7 +125,16 @@
def test_server_basicops(self):
self.add_keypair()
self.security_group = self._create_security_group()
- self.boot_instance()
+ security_groups = [{'name': self.security_group['name']}]
+ self.md = {'meta1': 'data1', 'meta2': 'data2', 'metaN': 'dataN'}
+ self.instance = self.create_server(
+ image_id=self.image_ref,
+ flavor=self.flavor_ref,
+ key_name=self.keypair['name'],
+ security_groups=security_groups,
+ config_drive=CONF.compute_feature_enabled.config_drive,
+ metadata=self.md,
+ wait_until='ACTIVE')
self.verify_ssh()
self.verify_metadata()
self.verify_metadata_on_config_drive()
diff --git a/tempest/scenario/test_server_multinode.py b/tempest/scenario/test_server_multinode.py
index 403804d..7e0e41c 100644
--- a/tempest/scenario/test_server_multinode.py
+++ b/tempest/scenario/test_server_multinode.py
@@ -60,15 +60,11 @@
servers = []
for host in hosts[:CONF.compute.min_compute_nodes]:
- create_kwargs = {
- 'availability_zone': '%(zone)s:%(host_name)s' % host
- }
-
# by getting to active state here, this means this has
# landed on the host in question.
- inst = self.create_server(image=CONF.compute.image_ref,
- flavor=CONF.compute.flavor_ref,
- create_kwargs=create_kwargs)
+ inst = self.create_server(
+ availability_zone='%(zone)s:%(host_name)s' % host,
+ wait_until='ACTIVE')
server = self.servers_client.show_server(inst['id'])['server']
servers.append(server)
diff --git a/tempest/scenario/test_shelve_instance.py b/tempest/scenario/test_shelve_instance.py
index 5778107..378ae9d 100644
--- a/tempest/scenario/test_shelve_instance.py
+++ b/tempest/scenario/test_shelve_instance.py
@@ -59,10 +59,6 @@
security_group = self._create_security_group()
security_groups = [{'name': security_group['name']}]
- create_kwargs = {
- 'key_name': keypair['name'],
- 'security_groups': security_groups
- }
if boot_from_volume:
volume = self.create_volume(size=CONF.volume.volume_size,
@@ -72,11 +68,17 @@
'volume_id': volume['id'],
'delete_on_termination': '0'}]
- create_kwargs['block_device_mapping'] = bd_map
- server = self.create_server(create_kwargs=create_kwargs)
+ server = self.create_server(
+ key_name=keypair['name'],
+ security_groups=security_groups,
+ block_device_mapping=bd_map,
+ wait_until='ACTIVE')
else:
- server = self.create_server(image=CONF.compute.image_ref,
- create_kwargs=create_kwargs)
+ server = self.create_server(
+ image_id=CONF.compute.image_ref,
+ key_name=keypair['name'],
+ security_groups=security_groups,
+ wait_until='ACTIVE')
instance_ip = self.get_server_or_ip(server)
timestamp = self.create_timestamp(instance_ip,
diff --git a/tempest/scenario/test_snapshot_pattern.py b/tempest/scenario/test_snapshot_pattern.py
index cd59334..f3b6558 100644
--- a/tempest/scenario/test_snapshot_pattern.py
+++ b/tempest/scenario/test_snapshot_pattern.py
@@ -36,14 +36,6 @@
"""
- def _boot_image(self, image_id, keypair, security_group):
- security_groups = [{'name': security_group['name']}]
- create_kwargs = {
- 'key_name': keypair['name'],
- 'security_groups': security_groups
- }
- return self.create_server(image=image_id, create_kwargs=create_kwargs)
-
@test.idempotent_id('608e604b-1d63-4a82-8e3e-91bc665c90b4')
@testtools.skipUnless(CONF.compute_feature_enabled.snapshot,
'Snapshotting is not available.')
@@ -54,8 +46,12 @@
security_group = self._create_security_group()
# boot an instance and create a timestamp file in it
- server = self._boot_image(CONF.compute.image_ref, keypair,
- security_group)
+ server = self.create_server(
+ image_id=CONF.compute.image_ref,
+ key_name=keypair['name'],
+ security_groups=[{'name': security_group['name']}],
+ wait_until='ACTIVE')
+
instance_ip = self.get_server_or_ip(server)
timestamp = self.create_timestamp(instance_ip,
private_key=keypair['private_key'])
@@ -64,8 +60,11 @@
snapshot_image = self.create_server_snapshot(server=server)
# boot a second instance from the snapshot
- server_from_snapshot = self._boot_image(snapshot_image['id'],
- keypair, security_group)
+ server_from_snapshot = self.create_server(
+ image_id=snapshot_image['id'],
+ key_name=keypair['name'],
+ security_groups=[{'name': security_group['name']}],
+ wait_until='ACTIVE')
# check the existence of the timestamp file in the second instance
server_from_snapshot_ip = self.get_server_or_ip(server_from_snapshot)
diff --git a/tempest/scenario/test_stamp_pattern.py b/tempest/scenario/test_stamp_pattern.py
index 05ae33e..faae800 100644
--- a/tempest/scenario/test_stamp_pattern.py
+++ b/tempest/scenario/test_stamp_pattern.py
@@ -64,14 +64,6 @@
self.snapshots_client.wait_for_snapshot_status(volume_snapshot['id'],
status)
- def _boot_image(self, image_id, keypair, security_group):
- security_groups = [{'name': security_group['name']}]
- create_kwargs = {
- 'key_name': keypair['name'],
- 'security_groups': security_groups
- }
- return self.create_server(image=image_id, create_kwargs=create_kwargs)
-
def _create_volume_snapshot(self, volume):
snapshot_name = data_utils.rand_name('scenario-snapshot')
snapshot = self.snapshots_client.create_snapshot(
@@ -135,8 +127,11 @@
# boot an instance and create a timestamp file in it
volume = self._create_volume()
- server = self._boot_image(CONF.compute.image_ref, keypair,
- security_group)
+ server = self.create_server(
+ image_id=CONF.compute.image_ref,
+ key_name=keypair['name'],
+ security_groups=security_group,
+ wait_until='ACTIVE')
# create and add floating IP to server1
ip_for_server = self.get_server_or_ip(server)
@@ -160,8 +155,10 @@
snapshot_id=volume_snapshot['id'])
# boot second instance from the snapshot(instance2)
- server_from_snapshot = self._boot_image(snapshot_image['id'],
- keypair, security_group)
+ server_from_snapshot = self.create_server(
+ image_id=snapshot_image['id'],
+ key_name=keypair['name'],
+ security_groups=security_group)
# create and add floating IP to server_from_snapshot
ip_for_snapshot = self.get_server_or_ip(server_from_snapshot)
diff --git a/tempest/scenario/test_volume_boot_pattern.py b/tempest/scenario/test_volume_boot_pattern.py
index 96e79e6..81ecda0 100644
--- a/tempest/scenario/test_volume_boot_pattern.py
+++ b/tempest/scenario/test_volume_boot_pattern.py
@@ -69,7 +69,10 @@
{'name': security_group['name']}]
create_kwargs.update(self._get_bdm(
vol_id, delete_on_termination=delete_on_termination))
- return self.create_server(image='', create_kwargs=create_kwargs)
+ return self.create_server(
+ image='',
+ wait_until='ACTIVE',
+ **create_kwargs)
def _create_snapshot_from_volume(self, vol_id):
snap_name = data_utils.rand_name('snapshot')
@@ -158,7 +161,8 @@
self._delete_server(instance)
# boot instance from EBS image
- instance = self.create_server(image=image['id'])
+ instance = self.create_server(
+ image_id=image['id'])
# just ensure that instance booted
# delete instance
diff --git a/tempest/services/compute/json/tenant_networks_client.py b/tempest/services/compute/json/tenant_networks_client.py
deleted file mode 100644
index 33166c0..0000000
--- a/tempest/services/compute/json/tenant_networks_client.py
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright 2015 NEC Corporation. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from oslo_serialization import jsonutils as json
-
-from tempest.api_schema.response.compute.v2_1 import tenant_networks as schema
-from tempest.common import service_client
-
-
-class TenantNetworksClient(service_client.ServiceClient):
-
- def list_tenant_networks(self):
- resp, body = self.get("os-tenant-networks")
- body = json.loads(body)
- self.validate_response(schema.list_tenant_networks, resp, body)
- return service_client.ResponseBody(resp, body)
-
- def show_tenant_network(self, network_id):
- resp, body = self.get("os-tenant-networks/%s" % network_id)
- body = json.loads(body)
- self.validate_response(schema.get_tenant_network, resp, body)
- return service_client.ResponseBody(resp, body)
diff --git a/tempest/services/compute/json/tenant_usages_client.py b/tempest/services/compute/json/tenant_usages_client.py
deleted file mode 100644
index 73b4706..0000000
--- a/tempest/services/compute/json/tenant_usages_client.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# Copyright 2013 NEC Corporation
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
-
-from tempest.api_schema.response.compute.v2_1 import tenant_usages as schema
-from tempest.common import service_client
-
-
-class TenantUsagesClient(service_client.ServiceClient):
-
- def list_tenant_usages(self, **params):
- url = 'os-simple-tenant-usage'
- if params:
- url += '?%s' % urllib.urlencode(params)
-
- resp, body = self.get(url)
- body = json.loads(body)
- self.validate_response(schema.list_tenant_usage, resp, body)
- return service_client.ResponseBody(resp, body)
-
- def show_tenant_usage(self, tenant_id, **params):
- url = 'os-simple-tenant-usage/%s' % tenant_id
- if params:
- url += '?%s' % urllib.urlencode(params)
-
- resp, body = self.get(url)
- body = json.loads(body)
- self.validate_response(schema.get_tenant_usage, resp, body)
- return service_client.ResponseBody(resp, body)
diff --git a/tempest/services/compute/json/versions_client.py b/tempest/services/compute/json/versions_client.py
deleted file mode 100644
index 48c0e8d..0000000
--- a/tempest/services/compute/json/versions_client.py
+++ /dev/null
@@ -1,55 +0,0 @@
-# Copyright (c) 2015 Hewlett-Packard Development Company, L.P.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from oslo_serialization import jsonutils as json
-from six.moves import urllib
-
-from tempest.api_schema.response.compute.v2_1 import versions as schema
-from tempest.common import service_client
-
-
-class VersionsClient(service_client.ServiceClient):
-
- def _get_base_version_url(self):
- # NOTE: The URL which is gotten from keystone's catalog contains
- # API version and project-id like "v2/{project-id}", but we need
- # to access the URL which doesn't contain them for getting API
- # versions. For that, here should use raw_request() instead of
- # get().
- endpoint = self.base_url
- url = urllib.parse.urlparse(endpoint)
- return '%s://%s/' % (url.scheme, url.netloc)
-
- def list_versions(self):
- version_url = self._get_base_version_url()
- resp, body = self.raw_request(version_url, 'GET')
- body = json.loads(body)
- self.validate_response(schema.list_versions, resp, body)
- return service_client.ResponseBody(resp, body)
-
- def get_version_by_url(self, version_url):
- """Get the version document by url.
-
- This gets the version document for a url, useful in testing
- the contents of things like /v2/ or /v2.1/ in Nova. That
- controller needs authenticated access, so we have to get
- ourselves a token before making the request.
-
- """
- # we need a token for this request
- resp, body = self.raw_request(version_url, 'GET',
- {'X-Auth-Token': self.token})
- body = json.loads(body)
- self.validate_response(schema.get_one_version, resp, body)
- return service_client.ResponseBody(resp, body)
diff --git a/tempest/services/image/v1/json/image_client.py b/tempest/services/image/v1/json/images_client.py
similarity index 99%
rename from tempest/services/image/v1/json/image_client.py
rename to tempest/services/image/v1/json/images_client.py
index d97da36..4884106 100644
--- a/tempest/services/image/v1/json/image_client.py
+++ b/tempest/services/image/v1/json/images_client.py
@@ -32,13 +32,13 @@
LOG = logging.getLogger(__name__)
-class ImageClient(service_client.ServiceClient):
+class ImagesClient(service_client.ServiceClient):
def __init__(self, auth_provider, catalog_type, region, endpoint_type=None,
build_interval=None, build_timeout=None,
disable_ssl_certificate_validation=None,
ca_certs=None, trace_requests=None):
- super(ImageClient, self).__init__(
+ super(ImagesClient, self).__init__(
auth_provider,
catalog_type,
region,
diff --git a/tempest/services/image/v2/json/image_client.py b/tempest/services/image/v2/json/images_client.py
similarity index 87%
rename from tempest/services/image/v2/json/image_client.py
rename to tempest/services/image/v2/json/images_client.py
index 492c7df..33bfcb8 100644
--- a/tempest/services/image/v2/json/image_client.py
+++ b/tempest/services/image/v2/json/images_client.py
@@ -22,13 +22,13 @@
from tempest.common import service_client
-class ImageClientV2(service_client.ServiceClient):
+class ImagesClientV2(service_client.ServiceClient):
def __init__(self, auth_provider, catalog_type, region, endpoint_type=None,
build_interval=None, build_timeout=None,
disable_ssl_certificate_validation=None, ca_certs=None,
trace_requests=None):
- super(ImageClientV2, self).__init__(
+ super(ImagesClientV2, self).__init__(
auth_provider,
catalog_type,
region,
@@ -178,17 +178,22 @@
body = json.loads(body)
return service_client.ResponseBody(resp, body)
- def add_image_member(self, image_id, member_id):
+ def add_image_member(self, image_id, **kwargs):
url = 'v2/images/%s/members' % image_id
- data = json.dumps({'member': member_id})
+ data = json.dumps(kwargs)
resp, body = self.post(url, data)
self.expected_success(200, resp.status)
body = json.loads(body)
return service_client.ResponseBody(resp, body)
- def update_image_member(self, image_id, member_id, body):
+ def update_image_member(self, image_id, member_id, **kwargs):
+ """Update an image member.
+
+ Available params: see http://developer.openstack.org/
+ api-ref-image-v2.html#updateImageMember-v2
+ """
url = 'v2/images/%s/members/%s' % (image_id, member_id)
- data = json.dumps(body)
+ data = json.dumps(kwargs)
resp, body = self.put(url, data)
self.expected_success(200, resp.status)
body = json.loads(body)
@@ -220,19 +225,13 @@
body = json.loads(body)
return service_client.ResponseBody(resp, body)
- def create_namespaces(self, namespace, **kwargs):
- params = {
- "namespace": namespace,
- }
+ def create_namespace(self, **kwargs):
+ """Create a namespace.
- for option in kwargs:
- value = kwargs.get(option)
- if isinstance(value, dict) or isinstance(value, tuple):
- params.update(value)
- else:
- params[option] = value
-
- data = json.dumps(params)
+ Available params: see http://developer.openstack.org/
+ api-ref-image-v2.html#createNamespace-v2
+ """
+ data = json.dumps(kwargs)
self._validate_schema(data)
resp, body = self.post('/v2/metadefs/namespaces', data)
@@ -240,25 +239,23 @@
body = json.loads(body)
return service_client.ResponseBody(resp, body)
- def show_namespaces(self, namespace):
+ def show_namespace(self, namespace):
url = '/v2/metadefs/namespaces/%s' % namespace
resp, body = self.get(url)
self.expected_success(200, resp.status)
body = json.loads(body)
return service_client.ResponseBody(resp, body)
- def update_namespaces(self, namespace, visibility, **kwargs):
- params = {
- "namespace": namespace,
- "visibility": visibility
- }
- for option in kwargs:
- value = kwargs.get(option)
- if isinstance(value, dict) or isinstance(value, tuple):
- params.update(value)
- else:
- params[option] = value
+ def update_namespace(self, namespace, **kwargs):
+ """Update a namespace.
+ Available params: see http://developer.openstack.org/
+ api-ref-image-v2.html#updateNamespace-v2
+ """
+ # NOTE: On Glance API, we need to pass namespace on both URI
+ # and a request body.
+ params = {'namespace': namespace}
+ params.update(kwargs)
data = json.dumps(params)
self._validate_schema(data)
url = '/v2/metadefs/namespaces/%s' % namespace
@@ -267,7 +264,7 @@
body = json.loads(body)
return service_client.ResponseBody(resp, body)
- def delete_namespaces(self, namespace):
+ def delete_namespace(self, namespace):
url = '/v2/metadefs/namespaces/%s' % namespace
resp, _ = self.delete(url)
self.expected_success(204, resp.status)
diff --git a/tempest/services/network/json/metering_labels_client.py b/tempest/services/network/json/metering_labels_client.py
new file mode 100644
index 0000000..2e5cdae
--- /dev/null
+++ b/tempest/services/network/json/metering_labels_client.py
@@ -0,0 +1,33 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.services.network.json import base
+
+
+class MeteringLabelsClient(base.BaseNetworkClient):
+
+ def create_metering_label(self, **kwargs):
+ uri = '/metering/metering-labels'
+ post_data = {'metering_label': kwargs}
+ return self.create_resource(uri, post_data)
+
+ def show_metering_label(self, metering_label_id, **fields):
+ uri = '/metering/metering-labels/%s' % metering_label_id
+ return self.show_resource(uri, **fields)
+
+ def delete_metering_label(self, metering_label_id):
+ uri = '/metering/metering-labels/%s' % metering_label_id
+ return self.delete_resource(uri)
+
+ def list_metering_labels(self, **filters):
+ uri = '/metering/metering-labels'
+ return self.list_resources(uri, **filters)
diff --git a/tempest/services/network/json/network_client.py b/tempest/services/network/json/network_client.py
index 5a4229c..b525143 100644
--- a/tempest/services/network/json/network_client.py
+++ b/tempest/services/network/json/network_client.py
@@ -35,23 +35,6 @@
quotas
"""
- def create_metering_label(self, **kwargs):
- uri = '/metering/metering-labels'
- post_data = {'metering_label': kwargs}
- return self.create_resource(uri, post_data)
-
- def show_metering_label(self, metering_label_id, **fields):
- uri = '/metering/metering-labels/%s' % metering_label_id
- return self.show_resource(uri, **fields)
-
- def delete_metering_label(self, metering_label_id):
- uri = '/metering/metering-labels/%s' % metering_label_id
- return self.delete_resource(uri)
-
- def list_metering_labels(self, **filters):
- uri = '/metering/metering-labels'
- return self.list_resources(uri, **filters)
-
def create_metering_label_rule(self, **kwargs):
uri = '/metering/metering-label-rules'
post_data = {'metering_label_rule': kwargs}
diff --git a/tempest/services/volume/json/__init__.py b/tempest/services/volume/base/__init__.py
similarity index 100%
copy from tempest/services/volume/json/__init__.py
copy to tempest/services/volume/base/__init__.py
diff --git a/tempest/services/volume/json/admin/__init__.py b/tempest/services/volume/base/admin/__init__.py
similarity index 100%
copy from tempest/services/volume/json/admin/__init__.py
copy to tempest/services/volume/base/admin/__init__.py
diff --git a/tempest/services/volume/json/admin/volume_hosts_client.py b/tempest/services/volume/base/admin/base_volume_hosts_client.py
similarity index 88%
rename from tempest/services/volume/json/admin/volume_hosts_client.py
rename to tempest/services/volume/base/admin/base_volume_hosts_client.py
index 939db59..97bb007 100644
--- a/tempest/services/volume/json/admin/volume_hosts_client.py
+++ b/tempest/services/volume/base/admin/base_volume_hosts_client.py
@@ -22,7 +22,7 @@
class BaseVolumeHostsClient(service_client.ServiceClient):
"""Client class to send CRUD Volume Hosts API requests"""
- def list_hosts(self, params=None):
+ def list_hosts(self, **params):
"""Lists all hosts."""
url = 'os-hosts'
@@ -33,7 +33,3 @@
body = json.loads(body)
self.expected_success(200, resp.status)
return service_client.ResponseBody(resp, body)
-
-
-class VolumeHostsClient(BaseVolumeHostsClient):
- """Client class to send CRUD Volume Host API V1 requests"""
diff --git a/tempest/services/volume/json/admin/volume_quotas_client.py b/tempest/services/volume/base/admin/base_volume_quotas_client.py
similarity index 77%
rename from tempest/services/volume/json/admin/volume_quotas_client.py
rename to tempest/services/volume/base/admin/base_volume_quotas_client.py
index f6d4a14..ad8ba03 100644
--- a/tempest/services/volume/json/admin/volume_quotas_client.py
+++ b/tempest/services/volume/base/admin/base_volume_quotas_client.py
@@ -50,21 +50,14 @@
body = self.show_quota_set(tenant_id, params={'usage': True})
return body
- def update_quota_set(self, tenant_id, gigabytes=None, volumes=None,
- snapshots=None):
- post_body = {}
+ def update_quota_set(self, tenant_id, **kwargs):
+ """Updates quota set
- if gigabytes is not None:
- post_body['gigabytes'] = gigabytes
-
- if volumes is not None:
- post_body['volumes'] = volumes
-
- if snapshots is not None:
- post_body['snapshots'] = snapshots
-
- post_body = jsonutils.dumps({'quota_set': post_body})
- resp, body = self.put('os-quota-sets/%s' % tenant_id, post_body)
+ Available params: see http://developer.openstack.org/
+ api-ref-blockstorage-v2.html#updateQuotas-v2
+ """
+ put_body = jsonutils.dumps({'quota_set': kwargs})
+ resp, body = self.put('os-quota-sets/%s' % tenant_id, put_body)
self.expected_success(200, resp.status)
body = jsonutils.loads(body)
return service_client.ResponseBody(resp, body)
@@ -74,7 +67,3 @@
resp, body = self.delete('os-quota-sets/%s' % tenant_id)
self.expected_success(200, resp.status)
return service_client.ResponseBody(resp, body)
-
-
-class VolumeQuotasClient(BaseVolumeQuotasClient):
- """Client class to send CRUD Volume Type API V1 requests"""
diff --git a/tempest/services/volume/json/admin/volume_services_client.py b/tempest/services/volume/base/admin/base_volume_services_client.py
similarity index 91%
rename from tempest/services/volume/json/admin/volume_services_client.py
rename to tempest/services/volume/base/admin/base_volume_services_client.py
index 798a642..1790421 100644
--- a/tempest/services/volume/json/admin/volume_services_client.py
+++ b/tempest/services/volume/base/admin/base_volume_services_client.py
@@ -30,7 +30,3 @@
body = json.loads(body)
self.expected_success(200, resp.status)
return service_client.ResponseBody(resp, body)
-
-
-class VolumesServicesClient(BaseVolumesServicesClient):
- """Volume V1 volume services client"""
diff --git a/tempest/services/volume/json/admin/volume_types_client.py b/tempest/services/volume/base/admin/base_volume_types_client.py
similarity index 98%
rename from tempest/services/volume/json/admin/volume_types_client.py
rename to tempest/services/volume/base/admin/base_volume_types_client.py
index f2da8a5..8fcf57c 100644
--- a/tempest/services/volume/json/admin/volume_types_client.py
+++ b/tempest/services/volume/base/admin/base_volume_types_client.py
@@ -182,7 +182,3 @@
"/types/%s/encryption/provider" % str(vol_type_id))
self.expected_success(202, resp.status)
return service_client.ResponseBody(resp, body)
-
-
-class VolumeTypesClient(BaseVolumeTypesClient):
- """Volume V1 Volume Types client"""
diff --git a/tempest/services/volume/json/availability_zone_client.py b/tempest/services/volume/base/base_availability_zone_client.py
similarity index 89%
rename from tempest/services/volume/json/availability_zone_client.py
rename to tempest/services/volume/base/base_availability_zone_client.py
index 36f95d6..d5a2fda 100644
--- a/tempest/services/volume/json/availability_zone_client.py
+++ b/tempest/services/volume/base/base_availability_zone_client.py
@@ -25,7 +25,3 @@
body = json.loads(body)
self.expected_success(200, resp.status)
return service_client.ResponseBody(resp, body)
-
-
-class VolumeAvailabilityZoneClient(BaseVolumeAvailabilityZoneClient):
- """Volume V1 availability zone client."""
diff --git a/tempest/services/volume/json/backups_client.py b/tempest/services/volume/base/base_backups_client.py
similarity index 98%
rename from tempest/services/volume/json/backups_client.py
rename to tempest/services/volume/base/base_backups_client.py
index 3045992..be926e6 100644
--- a/tempest/services/volume/json/backups_client.py
+++ b/tempest/services/volume/base/base_backups_client.py
@@ -16,7 +16,6 @@
import time
from oslo_serialization import jsonutils as json
-
from tempest_lib import exceptions as lib_exc
from tempest.common import service_client
@@ -124,7 +123,3 @@
if int(time.time()) - start_time >= self.build_timeout:
raise exceptions.TimeoutException
time.sleep(self.build_interval)
-
-
-class BackupsClient(BaseBackupsClient):
- """Volume V1 Backups client"""
diff --git a/tempest/services/volume/json/extensions_client.py b/tempest/services/volume/base/base_extensions_client.py
similarity index 91%
rename from tempest/services/volume/json/extensions_client.py
rename to tempest/services/volume/base/base_extensions_client.py
index d7d3197..afc3f6b 100644
--- a/tempest/services/volume/json/extensions_client.py
+++ b/tempest/services/volume/base/base_extensions_client.py
@@ -26,7 +26,3 @@
body = json.loads(body)
self.expected_success(200, resp.status)
return service_client.ResponseBody(resp, body)
-
-
-class ExtensionsClient(BaseExtensionsClient):
- """Volume V1 extensions client."""
diff --git a/tempest/services/volume/json/qos_client.py b/tempest/services/volume/base/base_qos_client.py
similarity index 98%
rename from tempest/services/volume/json/qos_client.py
rename to tempest/services/volume/base/base_qos_client.py
index c79168c..c7f6c6e 100644
--- a/tempest/services/volume/json/qos_client.py
+++ b/tempest/services/volume/base/base_qos_client.py
@@ -155,7 +155,3 @@
resp, body = self.get(url)
self.expected_success(202, resp.status)
return service_client.ResponseBody(resp, body)
-
-
-class QosSpecsClient(BaseQosSpecsClient):
- """Volume V1 QoS client."""
diff --git a/tempest/services/volume/json/snapshots_client.py b/tempest/services/volume/base/base_snapshots_client.py
similarity index 98%
rename from tempest/services/volume/json/snapshots_client.py
rename to tempest/services/volume/base/base_snapshots_client.py
index 77c9dd1..fac90e4 100644
--- a/tempest/services/volume/json/snapshots_client.py
+++ b/tempest/services/volume/base/base_snapshots_client.py
@@ -196,7 +196,3 @@
resp, body = self.post('snapshots/%s/action' % snapshot_id, post_body)
self.expected_success(202, resp.status)
return service_client.ResponseBody(resp, body)
-
-
-class SnapshotsClient(BaseSnapshotsClient):
- """Client class to send CRUD Volume V1 API requests."""
diff --git a/tempest/services/volume/json/volumes_client.py b/tempest/services/volume/base/base_volumes_client.py
similarity index 98%
rename from tempest/services/volume/json/volumes_client.py
rename to tempest/services/volume/base/base_volumes_client.py
index c2a76f0..c7302e8 100644
--- a/tempest/services/volume/json/volumes_client.py
+++ b/tempest/services/volume/base/base_volumes_client.py
@@ -334,7 +334,3 @@
post_body = json.dumps({'os-retype': post_body})
resp, body = self.post('volumes/%s/action' % volume_id, post_body)
self.expected_success(202, resp.status)
-
-
-class VolumesClient(BaseVolumesClient):
- """Client class to send CRUD Volume V1 API requests"""
diff --git a/tempest/services/volume/json/__init__.py b/tempest/services/volume/v1/__init__.py
similarity index 100%
copy from tempest/services/volume/json/__init__.py
copy to tempest/services/volume/v1/__init__.py
diff --git a/tempest/services/volume/json/__init__.py b/tempest/services/volume/v1/json/__init__.py
similarity index 100%
rename from tempest/services/volume/json/__init__.py
rename to tempest/services/volume/v1/json/__init__.py
diff --git a/tempest/services/volume/json/admin/__init__.py b/tempest/services/volume/v1/json/admin/__init__.py
similarity index 100%
rename from tempest/services/volume/json/admin/__init__.py
rename to tempest/services/volume/v1/json/admin/__init__.py
diff --git a/tempest/services/volume/v1/json/admin/volume_hosts_client.py b/tempest/services/volume/v1/json/admin/volume_hosts_client.py
new file mode 100644
index 0000000..e564469
--- /dev/null
+++ b/tempest/services/volume/v1/json/admin/volume_hosts_client.py
@@ -0,0 +1,20 @@
+# Copyright 2013 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.services.volume.base.admin import base_volume_hosts_client
+
+
+class VolumeHostsClient(base_volume_hosts_client.BaseVolumeHostsClient):
+ """Client class to send CRUD Volume Host API V1 requests"""
diff --git a/tempest/services/volume/v1/json/admin/volume_quotas_client.py b/tempest/services/volume/v1/json/admin/volume_quotas_client.py
new file mode 100644
index 0000000..b0dde19
--- /dev/null
+++ b/tempest/services/volume/v1/json/admin/volume_quotas_client.py
@@ -0,0 +1,19 @@
+# Copyright (C) 2014 eNovance SAS <licensing@enovance.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.services.volume.base.admin import base_volume_quotas_client
+
+
+class VolumeQuotasClient(base_volume_quotas_client.BaseVolumeQuotasClient):
+ """Client class to send CRUD Volume Type API V1 requests"""
diff --git a/tempest/services/volume/v1/json/admin/volume_services_client.py b/tempest/services/volume/v1/json/admin/volume_services_client.py
new file mode 100644
index 0000000..00810ed
--- /dev/null
+++ b/tempest/services/volume/v1/json/admin/volume_services_client.py
@@ -0,0 +1,21 @@
+# Copyright 2014 NEC Corporation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.services.volume.base.admin import base_volume_services_client
+
+
+class VolumesServicesClient(
+ base_volume_services_client.BaseVolumesServicesClient):
+ """Volume V1 volume services client"""
diff --git a/tempest/services/volume/v1/json/admin/volume_types_client.py b/tempest/services/volume/v1/json/admin/volume_types_client.py
new file mode 100644
index 0000000..28524d1
--- /dev/null
+++ b/tempest/services/volume/v1/json/admin/volume_types_client.py
@@ -0,0 +1,20 @@
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.services.volume.base.admin import base_volume_types_client
+
+
+class VolumeTypesClient(base_volume_types_client.BaseVolumeTypesClient):
+ """Volume V1 Volume Types client"""
diff --git a/tempest/services/volume/v1/json/availability_zone_client.py b/tempest/services/volume/v1/json/availability_zone_client.py
new file mode 100644
index 0000000..d8180fa
--- /dev/null
+++ b/tempest/services/volume/v1/json/availability_zone_client.py
@@ -0,0 +1,21 @@
+# Copyright 2014 NEC Corporation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.services.volume.base import base_availability_zone_client
+
+
+class VolumeAvailabilityZoneClient(
+ base_availability_zone_client.BaseVolumeAvailabilityZoneClient):
+ """Volume V1 availability zone client."""
diff --git a/tempest/services/volume/v1/json/backups_client.py b/tempest/services/volume/v1/json/backups_client.py
new file mode 100644
index 0000000..ac6db6a
--- /dev/null
+++ b/tempest/services/volume/v1/json/backups_client.py
@@ -0,0 +1,20 @@
+# Copyright 2014 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.services.volume.base import base_backups_client
+
+
+class BackupsClient(base_backups_client.BaseBackupsClient):
+ """Volume V1 Backups client"""
diff --git a/tempest/services/volume/json/extensions_client.py b/tempest/services/volume/v1/json/extensions_client.py
similarity index 61%
copy from tempest/services/volume/json/extensions_client.py
copy to tempest/services/volume/v1/json/extensions_client.py
index d7d3197..f99d0f5 100644
--- a/tempest/services/volume/json/extensions_client.py
+++ b/tempest/services/volume/v1/json/extensions_client.py
@@ -13,20 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-from oslo_serialization import jsonutils as json
-
-from tempest.common import service_client
+from tempest.services.volume.base import base_extensions_client
-class BaseExtensionsClient(service_client.ServiceClient):
-
- def list_extensions(self):
- url = 'extensions'
- resp, body = self.get(url)
- body = json.loads(body)
- self.expected_success(200, resp.status)
- return service_client.ResponseBody(resp, body)
-
-
-class ExtensionsClient(BaseExtensionsClient):
+class ExtensionsClient(base_extensions_client.BaseExtensionsClient):
"""Volume V1 extensions client."""
diff --git a/tempest/services/volume/v1/json/qos_client.py b/tempest/services/volume/v1/json/qos_client.py
new file mode 100644
index 0000000..b2b2195
--- /dev/null
+++ b/tempest/services/volume/v1/json/qos_client.py
@@ -0,0 +1,19 @@
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.services.volume.base import base_qos_client
+
+
+class QosSpecsClient(base_qos_client.BaseQosSpecsClient):
+ """Volume V1 QoS client."""
diff --git a/tempest/services/volume/v1/json/snapshots_client.py b/tempest/services/volume/v1/json/snapshots_client.py
new file mode 100644
index 0000000..b039c2b
--- /dev/null
+++ b/tempest/services/volume/v1/json/snapshots_client.py
@@ -0,0 +1,17 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.services.volume.base import base_snapshots_client
+
+
+class SnapshotsClient(base_snapshots_client.BaseSnapshotsClient):
+ """Client class to send CRUD Volume V1 API requests."""
diff --git a/tempest/services/volume/v1/json/volumes_client.py b/tempest/services/volume/v1/json/volumes_client.py
new file mode 100644
index 0000000..7782043
--- /dev/null
+++ b/tempest/services/volume/v1/json/volumes_client.py
@@ -0,0 +1,20 @@
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.services.volume.base import base_volumes_client
+
+
+class VolumesClient(base_volumes_client.BaseVolumesClient):
+ """Client class to send CRUD Volume V1 API requests"""
diff --git a/tempest/services/volume/v2/json/admin/volume_hosts_client.py b/tempest/services/volume/v2/json/admin/volume_hosts_client.py
index 649c9ec..a1d8b61 100644
--- a/tempest/services/volume/v2/json/admin/volume_hosts_client.py
+++ b/tempest/services/volume/v2/json/admin/volume_hosts_client.py
@@ -13,10 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-
-from tempest.services.volume.json.admin import volume_hosts_client
+from tempest.services.volume.base.admin import base_volume_hosts_client
-class VolumeHostsV2Client(volume_hosts_client.BaseVolumeHostsClient):
+class VolumeHostsV2Client(base_volume_hosts_client.BaseVolumeHostsClient):
"""Client class to send CRUD Volume V2 API requests"""
api_version = "v2"
diff --git a/tempest/services/volume/v2/json/admin/volume_quotas_client.py b/tempest/services/volume/v2/json/admin/volume_quotas_client.py
index 80fffc7..a89ba2f 100644
--- a/tempest/services/volume/v2/json/admin/volume_quotas_client.py
+++ b/tempest/services/volume/v2/json/admin/volume_quotas_client.py
@@ -13,9 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.services.volume.json.admin import volume_quotas_client
+from tempest.services.volume.base.admin import base_volume_quotas_client
-class VolumeQuotasV2Client(volume_quotas_client.BaseVolumeQuotasClient):
+class VolumeQuotasV2Client(base_volume_quotas_client.BaseVolumeQuotasClient):
"""Client class to send CRUD Volume V2 API requests"""
api_version = "v2"
diff --git a/tempest/services/volume/v2/json/admin/volume_services_client.py b/tempest/services/volume/v2/json/admin/volume_services_client.py
index 88eb82f..da7a4ea 100644
--- a/tempest/services/volume/v2/json/admin/volume_services_client.py
+++ b/tempest/services/volume/v2/json/admin/volume_services_client.py
@@ -13,9 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.services.volume.json.admin import volume_services_client as vs_cli
+from tempest.services.volume.base.admin import base_volume_services_client
-class VolumesServicesV2Client(vs_cli.BaseVolumesServicesClient):
+class VolumesServicesV2Client(
+ base_volume_services_client.BaseVolumesServicesClient):
"""Client class to send CRUD Volume V2 API requests"""
api_version = "v2"
diff --git a/tempest/services/volume/v2/json/admin/volume_types_client.py b/tempest/services/volume/v2/json/admin/volume_types_client.py
index 78fe8e5..d63acf5 100644
--- a/tempest/services/volume/v2/json/admin/volume_types_client.py
+++ b/tempest/services/volume/v2/json/admin/volume_types_client.py
@@ -13,10 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-
-from tempest.services.volume.json.admin import volume_types_client
+from tempest.services.volume.base.admin import base_volume_types_client
-class VolumeTypesV2Client(volume_types_client.BaseVolumeTypesClient):
+class VolumeTypesV2Client(base_volume_types_client.BaseVolumeTypesClient):
"""Client class to send CRUD Volume V2 API requests"""
api_version = "v2"
diff --git a/tempest/services/volume/v2/json/availability_zone_client.py b/tempest/services/volume/v2/json/availability_zone_client.py
index 2e1ab20..a4fc9fe 100644
--- a/tempest/services/volume/v2/json/availability_zone_client.py
+++ b/tempest/services/volume/v2/json/availability_zone_client.py
@@ -13,9 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.services.volume.json import availability_zone_client
+from tempest.services.volume.base import base_availability_zone_client
class VolumeV2AvailabilityZoneClient(
- availability_zone_client.BaseVolumeAvailabilityZoneClient):
+ base_availability_zone_client.BaseVolumeAvailabilityZoneClient):
api_version = "v2"
diff --git a/tempest/services/volume/v2/json/backups_client.py b/tempest/services/volume/v2/json/backups_client.py
index 8e87505..15d363b 100644
--- a/tempest/services/volume/v2/json/backups_client.py
+++ b/tempest/services/volume/v2/json/backups_client.py
@@ -13,9 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.services.volume.json import backups_client
+from tempest.services.volume.base import base_backups_client
-class BackupsClientV2(backups_client.BaseBackupsClient):
+class BackupsClientV2(base_backups_client.BaseBackupsClient):
"""Client class to send CRUD Volume V2 API requests"""
api_version = "v2"
diff --git a/tempest/services/volume/v2/json/extensions_client.py b/tempest/services/volume/v2/json/extensions_client.py
index 3e32c0c..004b232 100644
--- a/tempest/services/volume/v2/json/extensions_client.py
+++ b/tempest/services/volume/v2/json/extensions_client.py
@@ -13,8 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.services.volume.json import extensions_client
+from tempest.services.volume.base import base_extensions_client
-class ExtensionsV2Client(extensions_client.BaseExtensionsClient):
+class ExtensionsV2Client(base_extensions_client.BaseExtensionsClient):
api_version = "v2"
diff --git a/tempest/services/volume/v2/json/qos_client.py b/tempest/services/volume/v2/json/qos_client.py
index 42bd1c9..e8b680a 100644
--- a/tempest/services/volume/v2/json/qos_client.py
+++ b/tempest/services/volume/v2/json/qos_client.py
@@ -12,8 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.services.volume.json import qos_client
+from tempest.services.volume.base import base_qos_client
-class QosSpecsV2Client(qos_client.BaseQosSpecsClient):
+class QosSpecsV2Client(base_qos_client.BaseQosSpecsClient):
api_version = "v2"
diff --git a/tempest/services/volume/v2/json/snapshots_client.py b/tempest/services/volume/v2/json/snapshots_client.py
index a94f9cd..28a9e98 100644
--- a/tempest/services/volume/v2/json/snapshots_client.py
+++ b/tempest/services/volume/v2/json/snapshots_client.py
@@ -10,10 +10,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.services.volume.json import snapshots_client
+from tempest.services.volume.base import base_snapshots_client
-class SnapshotsV2Client(snapshots_client.BaseSnapshotsClient):
+class SnapshotsV2Client(base_snapshots_client.BaseSnapshotsClient):
"""Client class to send CRUD Volume V2 API requests."""
api_version = "v2"
create_resp = 202
diff --git a/tempest/services/volume/v2/json/volumes_client.py b/tempest/services/volume/v2/json/volumes_client.py
index a56a7be..51daa94 100644
--- a/tempest/services/volume/v2/json/volumes_client.py
+++ b/tempest/services/volume/v2/json/volumes_client.py
@@ -13,10 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.services.volume.json import volumes_client
+from tempest.services.volume.base import base_volumes_client
-class VolumesV2Client(volumes_client.BaseVolumesClient):
+class VolumesV2Client(base_volumes_client.BaseVolumesClient):
"""Client class to send CRUD Volume V2 API requests"""
api_version = "v2"
create_resp = 202
diff --git a/tempest/test.py b/tempest/test.py
index a9c8ba7..30eb93d 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -27,6 +27,7 @@
from oslo_serialization import jsonutils as json
from oslo_utils import importutils
import six
+from tempest_lib import decorators
import testscenarios
import testtools
@@ -43,6 +44,8 @@
CONF = config.CONF
+idempotent_id = decorators.idempotent_id
+
def attr(**kwargs):
"""A decorator which applies the testtools attr decorator
@@ -62,23 +65,6 @@
return decorator
-def idempotent_id(id):
- """Stub for metadata decorator"""
- if not isinstance(id, six.string_types):
- raise TypeError('Test idempotent_id must be string not %s'
- '' % type(id).__name__)
- uuid.UUID(id)
-
- def decorator(f):
- f = testtools.testcase.attr('id-%s' % id)(f)
- if f.__doc__:
- f.__doc__ = 'Test idempotent id: %s\n%s' % (id, f.__doc__)
- else:
- f.__doc__ = 'Test idempotent id: %s' % id
- return f
- return decorator
-
-
def get_service_list():
service_list = {
'compute': CONF.service_available.nova,
@@ -495,7 +481,7 @@
:param credential_type: string - primary, alt or admin
:param roles: list of roles
- :returns the created client manager
+ :returns: the created client manager
:raises skipException: if the requested credentials are not available
"""
if all([roles, credential_type]):
diff --git a/tempest/tests/common/test_service_clients.py b/tempest/tests/common/test_service_clients.py
index b0706f2..8de014f 100644
--- a/tempest/tests/common/test_service_clients.py
+++ b/tempest/tests/common/test_service_clients.py
@@ -36,8 +36,8 @@
from tempest.services.identity.v3.json import policy_client
from tempest.services.identity.v3.json import region_client
from tempest.services.identity.v3.json import service_client
-from tempest.services.image.v1.json import image_client
-from tempest.services.image.v2.json import image_client as image_v2_client
+from tempest.services.image.v1.json import images_client
+from tempest.services.image.v2.json import images_client as images_v2_client
from tempest.services.messaging.json import messaging_client
from tempest.services.network.json import network_client
from tempest.services.object_storage import account_client
@@ -46,18 +46,18 @@
from tempest.services.orchestration.json import orchestration_client
from tempest.services.telemetry.json import alarming_client
from tempest.services.telemetry.json import telemetry_client
-from tempest.services.volume.json.admin import volume_hosts_client
-from tempest.services.volume.json.admin import volume_quotas_client
-from tempest.services.volume.json.admin import volume_services_client
-from tempest.services.volume.json.admin import volume_types_client
-from tempest.services.volume.json import availability_zone_client \
+from tempest.services.volume.v1.json.admin import volume_hosts_client
+from tempest.services.volume.v1.json.admin import volume_quotas_client
+from tempest.services.volume.v1.json.admin import volume_services_client
+from tempest.services.volume.v1.json.admin import volume_types_client
+from tempest.services.volume.v1.json import availability_zone_client \
as volume_az_client
-from tempest.services.volume.json import backups_client
-from tempest.services.volume.json import extensions_client \
+from tempest.services.volume.v1.json import backups_client
+from tempest.services.volume.v1.json import extensions_client \
as volume_extensions_client
-from tempest.services.volume.json import qos_client
-from tempest.services.volume.json import snapshots_client
-from tempest.services.volume.json import volumes_client
+from tempest.services.volume.v1.json import qos_client
+from tempest.services.volume.v1.json import snapshots_client
+from tempest.services.volume.v1.json import volumes_client
from tempest.services.volume.v2.json.admin import volume_hosts_client \
as volume_v2_hosts_client
from tempest.services.volume.v2.json.admin import volume_quotas_client \
@@ -130,8 +130,8 @@
policy_client.PolicyClient,
region_client.RegionClient,
service_client.ServiceClient,
- image_client.ImageClient,
- image_v2_client.ImageClientV2
+ images_client.ImagesClient,
+ images_v2_client.ImagesClientV2
]
for client in test_clients:
diff --git a/tempest/tests/common/test_waiters.py b/tempest/tests/common/test_waiters.py
index 68a8295..c7cc638 100644
--- a/tempest/tests/common/test_waiters.py
+++ b/tempest/tests/common/test_waiters.py
@@ -18,7 +18,7 @@
from tempest.common import waiters
from tempest import exceptions
-from tempest.services.volume.json import volumes_client
+from tempest.services.volume.base import base_volumes_client
from tempest.tests import base
@@ -53,7 +53,7 @@
def test_wait_for_volume_status_error_restoring(self, mock_sleep):
# Tests that the wait method raises VolumeRestoreErrorException if
# the volume status is 'error_restoring'.
- client = mock.Mock(spec=volumes_client.BaseVolumesClient,
+ client = mock.Mock(spec=base_volumes_client.BaseVolumesClient,
build_interval=1)
volume1 = {'volume': {'status': 'restoring-backup'}}
volume2 = {'volume': {'status': 'error_restoring'}}
diff --git a/tempest/tests/common/utils/linux/test_remote_client.py b/tempest/tests/common/utils/linux/test_remote_client.py
index 3ff8e0d..e596aab 100644
--- a/tempest/tests/common/utils/linux/test_remote_client.py
+++ b/tempest/tests/common/utils/linux/test_remote_client.py
@@ -146,8 +146,11 @@
self._assert_exec_called_with(
"sudo ip addr add %s/%s dev %s" % (ip, '28', nic))
- def test_turn_nic_on(self):
+ def test_set_nic_state(self):
nic = 'eth0'
- self.conn.turn_nic_on(nic)
+ self.conn.set_nic_state(nic)
self._assert_exec_called_with(
'sudo ip link set %s up' % nic)
+ self.conn.set_nic_state(nic, "down")
+ self._assert_exec_called_with(
+ 'sudo ip link set %s down' % nic)
diff --git a/tempest/tests/services/compute/test_tenant_networks_client.py b/tempest/tests/services/compute/test_tenant_networks_client.py
deleted file mode 100644
index 691792a..0000000
--- a/tempest/tests/services/compute/test_tenant_networks_client.py
+++ /dev/null
@@ -1,64 +0,0 @@
-# Copyright 2015 NEC Corporation. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from tempest_lib.tests import fake_auth_provider
-
-from tempest.services.compute.json import tenant_networks_client
-from tempest.tests.services.compute import base
-
-
-class TestTenantNetworksClient(base.BaseComputeServiceTest):
-
- FAKE_NETWORK = {
- "cidr": "None",
- "id": "c2329eb4-cc8e-4439-ac4c-932369309e36",
- "label": u'\u30d7'
- }
-
- FAKE_NETWORKS = [FAKE_NETWORK]
-
- NETWORK_ID = FAKE_NETWORK['id']
-
- def setUp(self):
- super(TestTenantNetworksClient, self).setUp()
- fake_auth = fake_auth_provider.FakeAuthProvider()
- self.client = tenant_networks_client.TenantNetworksClient(
- fake_auth, 'compute', 'regionOne')
-
- def _test_list_tenant_networks(self, bytes_body=False):
- self.check_service_client_function(
- self.client.list_tenant_networks,
- 'tempest.common.service_client.ServiceClient.get',
- {"networks": self.FAKE_NETWORKS},
- bytes_body)
-
- def test_list_tenant_networks_with_str_body(self):
- self._test_list_tenant_networks()
-
- def test_list_tenant_networks_with_bytes_body(self):
- self._test_list_tenant_networks(bytes_body=True)
-
- def _test_show_tenant_network(self, bytes_body=False):
- self.check_service_client_function(
- self.client.show_tenant_network,
- 'tempest.common.service_client.ServiceClient.get',
- {"network": self.FAKE_NETWORK},
- bytes_body,
- network_id=self.NETWORK_ID)
-
- def test_show_tenant_network_with_str_body(self):
- self._test_show_tenant_network()
-
- def test_show_tenant_network_with_bytes_body(self):
- self._test_show_tenant_network(bytes_body=True)
diff --git a/tempest/tests/services/compute/test_tenant_usages_client.py b/tempest/tests/services/compute/test_tenant_usages_client.py
deleted file mode 100644
index 58e0b7a..0000000
--- a/tempest/tests/services/compute/test_tenant_usages_client.py
+++ /dev/null
@@ -1,80 +0,0 @@
-# Copyright 2015 NEC Corporation. All rights reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from tempest_lib.tests import fake_auth_provider
-
-from tempest.services.compute.json import tenant_usages_client
-from tempest.tests.services.compute import base
-
-
-class TestTenantUsagesClient(base.BaseComputeServiceTest):
-
- FAKE_SERVER_USAGES = [{
- "ended_at": None,
- "flavor": "m1.tiny",
- "hours": 1.0,
- "instance_id": "1f1deceb-17b5-4c04-84c7-e0d4499c8fe0",
- "local_gb": 1,
- "memory_mb": 512,
- "name": "new-server-test",
- "started_at": "2012-10-08T20:10:44.541277",
- "state": "active",
- "tenant_id": "openstack",
- "uptime": 3600,
- "vcpus": 1
- }]
-
- FAKE_TENANT_USAGES = [{
- "server_usages": FAKE_SERVER_USAGES,
- "start": "2012-10-08T21:10:44.587336",
- "stop": "2012-10-08T22:10:44.587336",
- "tenant_id": "openstack",
- "total_hours": 1,
- "total_local_gb_usage": 1,
- "total_memory_mb_usage": 512,
- "total_vcpus_usage": 1
- }]
-
- def setUp(self):
- super(TestTenantUsagesClient, self).setUp()
- fake_auth = fake_auth_provider.FakeAuthProvider()
- self.client = tenant_usages_client.TenantUsagesClient(
- fake_auth, 'compute', 'regionOne')
-
- def _test_list_tenant_usages(self, bytes_body=False):
- self.check_service_client_function(
- self.client.list_tenant_usages,
- 'tempest.common.service_client.ServiceClient.get',
- {"tenant_usages": self.FAKE_TENANT_USAGES},
- to_utf=bytes_body)
-
- def test_list_tenant_usages_with_str_body(self):
- self._test_list_tenant_usages()
-
- def test_list_tenant_usages_with_bytes_body(self):
- self._test_list_tenant_usages(bytes_body=True)
-
- def _test_show_tenant_usage(self, bytes_body=False):
- self.check_service_client_function(
- self.client.show_tenant_usage,
- 'tempest.common.service_client.ServiceClient.get',
- {"tenant_usage": self.FAKE_TENANT_USAGES[0]},
- to_utf=bytes_body,
- tenant_id='openstack')
-
- def test_show_tenant_usage_with_str_body(self):
- self._test_show_tenant_usage()
-
- def test_show_tenant_usage_with_bytes_body(self):
- self._test_show_tenant_usage(bytes_body=True)
diff --git a/tempest/tests/test_glance_http.py b/tempest/tests/test_glance_http.py
index 105caec..ed886da 100644
--- a/tempest/tests/test_glance_http.py
+++ b/tempest/tests/test_glance_http.py
@@ -13,14 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-import socket
-
import mock
-from oslo_serialization import jsonutils as json
from oslotest import mockpatch
import six
from six.moves import http_client as httplib
-from tempest_lib import exceptions as lib_exc
from tempest.common import glance_http
from tempest import exceptions
@@ -56,60 +52,6 @@
'getresponse', return_value=resp))
return resp
- def test_json_request_without_content_type_header_in_response(self):
- self._set_response_fixture({}, 200, 'fake_response_body')
- self.assertRaises(lib_exc.InvalidContentType,
- self.client.json_request, 'GET', '/images')
-
- def test_json_request_with_xml_content_type_header_in_request(self):
- self.assertRaises(lib_exc.InvalidContentType,
- self.client.json_request, 'GET', '/images',
- headers={'Content-Type': 'application/xml'})
-
- def test_json_request_with_xml_content_type_header_in_response(self):
- self._set_response_fixture({'content-type': 'application/xml'},
- 200, 'fake_response_body')
- self.assertRaises(lib_exc.InvalidContentType,
- self.client.json_request, 'GET', '/images')
-
- def test_json_request_with_json_content_type_header_only_in_resp(self):
- self._set_response_fixture({'content-type': 'application/json'},
- 200, 'fake_response_body')
- resp, body = self.client.json_request('GET', '/images')
- self.assertEqual(200, resp.status)
- self.assertEqual('fake_response_body', body)
-
- def test_json_request_with_json_content_type_header_in_req_and_resp(self):
- self._set_response_fixture({'content-type': 'application/json'},
- 200, 'fake_response_body')
- resp, body = self.client.json_request('GET', '/images', headers={
- 'Content-Type': 'application/json'})
- self.assertEqual(200, resp.status)
- self.assertEqual('fake_response_body', body)
-
- def test_json_request_fails_to_json_loads(self):
- self._set_response_fixture({'content-type': 'application/json'},
- 200, 'fake_response_body')
- self.useFixture(mockpatch.PatchObject(json, 'loads',
- side_effect=ValueError()))
- resp, body = self.client.json_request('GET', '/images')
- self.assertEqual(200, resp.status)
- self.assertEqual(body, 'fake_response_body')
-
- def test_json_request_socket_timeout(self):
- self.useFixture(mockpatch.PatchObject(httplib.HTTPConnection,
- 'request',
- side_effect=socket.timeout()))
- self.assertRaises(exceptions.TimeoutException,
- self.client.json_request, 'GET', '/images')
-
- def test_json_request_endpoint_not_found(self):
- self.useFixture(mockpatch.PatchObject(httplib.HTTPConnection,
- 'request',
- side_effect=socket.gaierror()))
- self.assertRaises(exceptions.EndpointNotFound,
- self.client.json_request, 'GET', '/images')
-
def test_raw_request(self):
self._set_response_fixture({}, 200, 'fake_response_body')
resp, body = self.client.raw_request('GET', '/images')
@@ -141,22 +83,22 @@
self.assertEqual(call_count - 1, req_body.tell())
def test_get_connection_class_for_https(self):
- conn_class = self.client.get_connection_class('https')
+ conn_class = self.client._get_connection_class('https')
self.assertEqual(glance_http.VerifiedHTTPSConnection, conn_class)
def test_get_connection_class_for_http(self):
- conn_class = (self.client.get_connection_class('http'))
+ conn_class = (self.client._get_connection_class('http'))
self.assertEqual(httplib.HTTPConnection, conn_class)
def test_get_connection_http(self):
- self.assertTrue(isinstance(self.client.get_connection(),
+ self.assertTrue(isinstance(self.client._get_connection(),
httplib.HTTPConnection))
def test_get_connection_https(self):
endpoint = 'https://fake_url.com'
self.fake_auth.base_url = mock.MagicMock(return_value=endpoint)
self.client = glance_http.HTTPClient(self.fake_auth, {})
- self.assertTrue(isinstance(self.client.get_connection(),
+ self.assertTrue(isinstance(self.client._get_connection(),
glance_http.VerifiedHTTPSConnection))
def test_get_connection_url_not_fount(self):
@@ -164,22 +106,22 @@
side_effect=httplib.InvalidURL()
))
self.assertRaises(exceptions.EndpointNotFound,
- self.client.get_connection)
+ self.client._get_connection)
def test_get_connection_kwargs_default_for_http(self):
- kwargs = self.client.get_connection_kwargs('http')
+ kwargs = self.client._get_connection_kwargs('http')
self.assertEqual(600, kwargs['timeout'])
self.assertEqual(1, len(kwargs.keys()))
def test_get_connection_kwargs_set_timeout_for_http(self):
- kwargs = self.client.get_connection_kwargs('http', timeout=10,
- ca_certs='foo')
+ kwargs = self.client._get_connection_kwargs('http', timeout=10,
+ ca_certs='foo')
self.assertEqual(10, kwargs['timeout'])
# nothing more than timeout is evaluated for http connections
self.assertEqual(1, len(kwargs.keys()))
def test_get_connection_kwargs_default_for_https(self):
- kwargs = self.client.get_connection_kwargs('https')
+ kwargs = self.client._get_connection_kwargs('https')
self.assertEqual(600, kwargs['timeout'])
self.assertEqual(None, kwargs['ca_certs'])
self.assertEqual(None, kwargs['cert_file'])
@@ -189,12 +131,12 @@
self.assertEqual(6, len(kwargs.keys()))
def test_get_connection_kwargs_set_params_for_https(self):
- kwargs = self.client.get_connection_kwargs('https', timeout=10,
- ca_certs='foo',
- cert_file='/foo/bar.cert',
- key_file='/foo/key.pem',
- insecure=True,
- ssl_compression=False)
+ kwargs = self.client._get_connection_kwargs('https', timeout=10,
+ ca_certs='foo',
+ cert_file='/foo/bar.cert',
+ key_file='/foo/key.pem',
+ insecure=True,
+ ssl_compression=False)
self.assertEqual(10, kwargs['timeout'])
self.assertEqual('foo', kwargs['ca_certs'])
self.assertEqual('/foo/bar.cert', kwargs['cert_file'])
diff --git a/tools/check_uuid.py b/tools/check_uuid.py
deleted file mode 100755
index a71ad39..0000000
--- a/tools/check_uuid.py
+++ /dev/null
@@ -1,358 +0,0 @@
-#!/usr/bin/env python
-
-# Copyright 2014 Mirantis, Inc.
-#
-# 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 argparse
-import ast
-import importlib
-import inspect
-import os
-import sys
-import unittest
-import urllib
-import uuid
-
-DECORATOR_MODULE = 'test'
-DECORATOR_NAME = 'idempotent_id'
-DECORATOR_IMPORT = 'tempest.%s' % DECORATOR_MODULE
-IMPORT_LINE = 'from tempest import %s' % DECORATOR_MODULE
-DECORATOR_TEMPLATE = "@%s.%s('%%s')" % (DECORATOR_MODULE,
- DECORATOR_NAME)
-UNIT_TESTS_EXCLUDE = 'tempest.tests'
-
-
-class SourcePatcher(object):
-
- """"Lazy patcher for python source files"""
-
- def __init__(self):
- self.source_files = None
- self.patches = None
- self.clear()
-
- def clear(self):
- """Clear inner state"""
- self.source_files = {}
- self.patches = {}
-
- @staticmethod
- def _quote(s):
- return urllib.quote(s)
-
- @staticmethod
- def _unquote(s):
- return urllib.unquote(s)
-
- def add_patch(self, filename, patch, line_no):
- """Add lazy patch"""
- if filename not in self.source_files:
- with open(filename) as f:
- self.source_files[filename] = self._quote(f.read())
- patch_id = str(uuid.uuid4())
- if not patch.endswith('\n'):
- patch += '\n'
- self.patches[patch_id] = self._quote(patch)
- lines = self.source_files[filename].split(self._quote('\n'))
- lines[line_no - 1] = ''.join(('{%s:s}' % patch_id, lines[line_no - 1]))
- self.source_files[filename] = self._quote('\n').join(lines)
-
- def _save_changes(self, filename, source):
- print('%s fixed' % filename)
- with open(filename, 'w') as f:
- f.write(source)
-
- def apply_patches(self):
- """Apply all patches"""
- for filename in self.source_files:
- patched_source = self._unquote(
- self.source_files[filename].format(**self.patches)
- )
- self._save_changes(filename, patched_source)
- self.clear()
-
-
-class TestChecker(object):
-
- def __init__(self, package):
- self.package = package
- self.base_path = os.path.abspath(os.path.dirname(package.__file__))
-
- def _path_to_package(self, path):
- relative_path = path[len(self.base_path) + 1:]
- if relative_path:
- return '.'.join((self.package.__name__,) +
- tuple(relative_path.split('/')))
- else:
- return self.package.__name__
-
- def _modules_search(self):
- """Recursive search for python modules in base package"""
- modules = []
- for root, dirs, files in os.walk(self.base_path):
- if not os.path.exists(os.path.join(root, '__init__.py')):
- continue
- root_package = self._path_to_package(root)
- for item in files:
- if item.endswith('.py'):
- module_name = '.'.join((root_package,
- os.path.splitext(item)[0]))
- if not module_name.startswith(UNIT_TESTS_EXCLUDE):
- modules.append(module_name)
- return modules
-
- @staticmethod
- def _get_idempotent_id(test_node):
- # Return key-value dict with all metadata from @test.idempotent_id
- # decorators for test method
- idempotent_id = None
- for decorator in test_node.decorator_list:
- if (hasattr(decorator, 'func') and
- hasattr(decorator.func, 'attr') and
- decorator.func.attr == DECORATOR_NAME and
- hasattr(decorator.func, 'value') and
- decorator.func.value.id == DECORATOR_MODULE):
- for arg in decorator.args:
- idempotent_id = ast.literal_eval(arg)
- return idempotent_id
-
- @staticmethod
- def _is_decorator(line):
- return line.strip().startswith('@')
-
- @staticmethod
- def _is_def(line):
- return line.strip().startswith('def ')
-
- def _add_uuid_to_test(self, patcher, test_node, source_path):
- with open(source_path) as src:
- src_lines = src.read().split('\n')
- lineno = test_node.lineno
- insert_position = lineno
- while True:
- if (self._is_def(src_lines[lineno - 1]) or
- (self._is_decorator(src_lines[lineno - 1]) and
- (DECORATOR_TEMPLATE.split('(')[0] <=
- src_lines[lineno - 1].strip().split('(')[0]))):
- insert_position = lineno
- break
- lineno += 1
- patcher.add_patch(
- source_path,
- ' ' * test_node.col_offset + DECORATOR_TEMPLATE % uuid.uuid4(),
- insert_position
- )
-
- @staticmethod
- def _is_test_case(module, node):
- if (node.__class__ is ast.ClassDef and
- hasattr(module, node.name) and
- inspect.isclass(getattr(module, node.name))):
- return issubclass(getattr(module, node.name), unittest.TestCase)
-
- @staticmethod
- def _is_test_method(node):
- return (node.__class__ is ast.FunctionDef
- and node.name.startswith('test_'))
-
- @staticmethod
- def _next_node(body, node):
- if body.index(node) < len(body):
- return body[body.index(node) + 1]
-
- @staticmethod
- def _import_name(node):
- if type(node) == ast.Import:
- return node.names[0].name
- elif type(node) == ast.ImportFrom:
- return '%s.%s' % (node.module, node.names[0].name)
-
- def _add_import_for_test_uuid(self, patcher, src_parsed, source_path):
- with open(source_path) as f:
- src_lines = f.read().split('\n')
- line_no = 0
- tempest_imports = [node for node in src_parsed.body
- if self._import_name(node) and
- 'tempest.' in self._import_name(node)]
- if not tempest_imports:
- import_snippet = '\n'.join(('', IMPORT_LINE, ''))
- else:
- for node in tempest_imports:
- if self._import_name(node) < DECORATOR_IMPORT:
- continue
- else:
- line_no = node.lineno
- import_snippet = IMPORT_LINE
- break
- else:
- line_no = tempest_imports[-1].lineno
- while True:
- if (not src_lines[line_no - 1] or
- getattr(self._next_node(src_parsed.body,
- tempest_imports[-1]),
- 'lineno') == line_no or
- line_no == len(src_lines)):
- break
- line_no += 1
- import_snippet = '\n'.join((IMPORT_LINE, ''))
- patcher.add_patch(source_path, import_snippet, line_no)
-
- def get_tests(self):
- """Get test methods with sources from base package with metadata"""
- tests = {}
- for module_name in self._modules_search():
- tests[module_name] = {}
- module = importlib.import_module(module_name)
- source_path = '.'.join(
- (os.path.splitext(module.__file__)[0], 'py')
- )
- with open(source_path, 'r') as f:
- source = f.read()
- tests[module_name]['source_path'] = source_path
- tests[module_name]['tests'] = {}
- source_parsed = ast.parse(source)
- tests[module_name]['ast'] = source_parsed
- tests[module_name]['import_valid'] = (
- hasattr(module, DECORATOR_MODULE) and
- inspect.ismodule(getattr(module, DECORATOR_MODULE))
- )
- test_cases = (node for node in source_parsed.body
- if self._is_test_case(module, node))
- for node in test_cases:
- for subnode in filter(self._is_test_method, node.body):
- test_name = '%s.%s' % (node.name, subnode.name)
- tests[module_name]['tests'][test_name] = subnode
- return tests
-
- @staticmethod
- def _filter_tests(function, tests):
- """Filter tests with condition 'function(test_node) == True'"""
- result = {}
- for module_name in tests:
- for test_name in tests[module_name]['tests']:
- if function(module_name, test_name, tests):
- if module_name not in result:
- result[module_name] = {
- 'ast': tests[module_name]['ast'],
- 'source_path': tests[module_name]['source_path'],
- 'import_valid': tests[module_name]['import_valid'],
- 'tests': {}
- }
- result[module_name]['tests'][test_name] = \
- tests[module_name]['tests'][test_name]
- return result
-
- def find_untagged(self, tests):
- """Filter all tests without uuid in metadata"""
- def check_uuid_in_meta(module_name, test_name, tests):
- idempotent_id = self._get_idempotent_id(
- tests[module_name]['tests'][test_name])
- return not idempotent_id
- return self._filter_tests(check_uuid_in_meta, tests)
-
- def report_collisions(self, tests):
- """Reports collisions if there are any.
-
- Returns true if collisions exist.
- """
- uuids = {}
-
- def report(module_name, test_name, tests):
- test_uuid = self._get_idempotent_id(
- tests[module_name]['tests'][test_name])
- if not test_uuid:
- return
- if test_uuid in uuids:
- error_str = "%s:%s\n uuid %s collision: %s<->%s\n%s:%s" % (
- tests[module_name]['source_path'],
- tests[module_name]['tests'][test_name].lineno,
- test_uuid,
- test_name,
- uuids[test_uuid]['test_name'],
- uuids[test_uuid]['source_path'],
- uuids[test_uuid]['test_node'].lineno,
- )
- print(error_str)
- print("cannot automatically resolve the collision, please "
- "manually remove the duplicate value on the new test.")
- return True
- else:
- uuids[test_uuid] = {
- 'module': module_name,
- 'test_name': test_name,
- 'test_node': tests[module_name]['tests'][test_name],
- 'source_path': tests[module_name]['source_path']
- }
- return bool(self._filter_tests(report, tests))
-
- def report_untagged(self, tests):
- """Reports untagged tests if there are any.
-
- Returns true if untagged tests exist.
- """
- def report(module_name, test_name, tests):
- error_str = "%s:%s\nmissing @test.idempotent_id('...')\n%s\n" % (
- tests[module_name]['source_path'],
- tests[module_name]['tests'][test_name].lineno,
- test_name
- )
- print(error_str)
- return True
- return bool(self._filter_tests(report, tests))
-
- def fix_tests(self, tests):
- """Add uuids to all tests specified in tests and fix it"""
- patcher = SourcePatcher()
- for module_name in tests:
- add_import_once = True
- for test_name in tests[module_name]['tests']:
- if not tests[module_name]['import_valid'] and add_import_once:
- self._add_import_for_test_uuid(
- patcher,
- tests[module_name]['ast'],
- tests[module_name]['source_path']
- )
- add_import_once = False
- self._add_uuid_to_test(
- patcher, tests[module_name]['tests'][test_name],
- tests[module_name]['source_path'])
- patcher.apply_patches()
-
-
-def run():
- parser = argparse.ArgumentParser()
- parser.add_argument('--package', action='store', dest='package',
- default='tempest', type=str,
- help='Package with tests')
- parser.add_argument('--fix', action='store_true', dest='fix_tests',
- help='Attempt to fix tests without UUIDs')
- args = parser.parse_args()
- sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
- pkg = importlib.import_module(args.package)
- checker = TestChecker(pkg)
- errors = False
- tests = checker.get_tests()
- untagged = checker.find_untagged(tests)
- errors = checker.report_collisions(tests) or errors
- if args.fix_tests and untagged:
- checker.fix_tests(untagged)
- else:
- errors = checker.report_untagged(untagged) or errors
- if errors:
- sys.exit("@test.idempotent_id existence and uniqueness checks failed\n"
- "Run 'tox -v -euuidgen' to automatically fix tests with\n"
- "missing @test.idempotent_id decorators.")
-
-if __name__ == '__main__':
- run()
diff --git a/tools/colorizer.py b/tools/colorizer.py
deleted file mode 100755
index 3f68a51..0000000
--- a/tools/colorizer.py
+++ /dev/null
@@ -1,328 +0,0 @@
-#!/usr/bin/env python
-
-# Copyright (c) 2013, Nebula, Inc.
-# Copyright 2010 United States Government as represented by the
-# Administrator of the National Aeronautics and Space Administration.
-# 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.
-#
-# Colorizer Code is borrowed from Twisted:
-# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
-#
-# Permission is hereby granted, free of charge, to any person obtaining
-# a copy of this software and associated documentation files (the
-# "Software"), to deal in the Software without restriction, including
-# without limitation the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the Software, and to
-# permit persons to whom the Software is furnished to do so, subject to
-# the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-"""Display a subunit stream through a colorized unittest test runner."""
-
-import heapq
-import sys
-import unittest
-
-import subunit
-import testtools
-
-
-class _AnsiColorizer(object):
- """A colorizer is an object that loosely wraps around a stream
-
- allowing callers to write text to the stream in a particular color.
-
- Colorizer classes must implement C{supported()} and C{write(text, color)}.
- """
- _colors = dict(black=30, red=31, green=32, yellow=33,
- blue=34, magenta=35, cyan=36, white=37)
-
- def __init__(self, stream):
- self.stream = stream
-
- def supported(cls, stream=sys.stdout):
- """Check the current platform supports coloring terminal output
-
- A class method that returns True if the current platform supports
- coloring terminal output using this method. Returns False otherwise.
- """
- if not stream.isatty():
- return False # auto color only on TTYs
- try:
- import curses
- except ImportError:
- return False
- else:
- try:
- try:
- return curses.tigetnum("colors") > 2
- except curses.error:
- curses.setupterm()
- return curses.tigetnum("colors") > 2
- except Exception:
- # guess false in case of error
- return False
- supported = classmethod(supported)
-
- def write(self, text, color):
- """Write the given text to the stream in the given color.
-
- @param text: Text to be written to the stream.
-
- @param color: A string label for a color. e.g. 'red', 'white'.
- """
- color = self._colors[color]
- self.stream.write('\x1b[%s;1m%s\x1b[0m' % (color, text))
-
-
-class _Win32Colorizer(object):
- """See _AnsiColorizer docstring."""
- def __init__(self, stream):
- import win32console
- red, green, blue, bold = (win32console.FOREGROUND_RED,
- win32console.FOREGROUND_GREEN,
- win32console.FOREGROUND_BLUE,
- win32console.FOREGROUND_INTENSITY)
- self.stream = stream
- self.screenBuffer = win32console.GetStdHandle(
- win32console.STD_OUT_HANDLE)
- self._colors = {'normal': red | green | blue,
- 'red': red | bold,
- 'green': green | bold,
- 'blue': blue | bold,
- 'yellow': red | green | bold,
- 'magenta': red | blue | bold,
- 'cyan': green | blue | bold,
- 'white': red | green | blue | bold}
-
- def supported(cls, stream=sys.stdout):
- try:
- import win32console
- screenBuffer = win32console.GetStdHandle(
- win32console.STD_OUT_HANDLE)
- except ImportError:
- return False
- import pywintypes
- try:
- screenBuffer.SetConsoleTextAttribute(
- win32console.FOREGROUND_RED |
- win32console.FOREGROUND_GREEN |
- win32console.FOREGROUND_BLUE)
- except pywintypes.error:
- return False
- else:
- return True
- supported = classmethod(supported)
-
- def write(self, text, color):
- color = self._colors[color]
- self.screenBuffer.SetConsoleTextAttribute(color)
- self.stream.write(text)
- self.screenBuffer.SetConsoleTextAttribute(self._colors['normal'])
-
-
-class _NullColorizer(object):
- """See _AnsiColorizer docstring."""
- def __init__(self, stream):
- self.stream = stream
-
- def supported(cls, stream=sys.stdout):
- return True
- supported = classmethod(supported)
-
- def write(self, text, color):
- self.stream.write(text)
-
-
-def get_elapsed_time_color(elapsed_time):
- if elapsed_time > 1.0:
- return 'red'
- elif elapsed_time > 0.25:
- return 'yellow'
- else:
- return 'green'
-
-
-class NovaTestResult(testtools.TestResult):
- def __init__(self, stream, descriptions, verbosity):
- super(NovaTestResult, self).__init__()
- self.stream = stream
- self.showAll = verbosity > 1
- self.num_slow_tests = 10
- self.slow_tests = [] # this is a fixed-sized heap
- self.colorizer = None
- # NOTE(vish): reset stdout for the terminal check
- stdout = sys.stdout
- sys.stdout = sys.__stdout__
- for colorizer in [_Win32Colorizer, _AnsiColorizer, _NullColorizer]:
- if colorizer.supported():
- self.colorizer = colorizer(self.stream)
- break
- sys.stdout = stdout
- self.start_time = None
- self.last_time = {}
- self.results = {}
- self.last_written = None
-
- def _writeElapsedTime(self, elapsed):
- color = get_elapsed_time_color(elapsed)
- self.colorizer.write(" %.2f" % elapsed, color)
-
- def _addResult(self, test, *args):
- try:
- name = test.id()
- except AttributeError:
- name = 'Unknown.unknown'
- test_class, test_name = name.rsplit('.', 1)
-
- elapsed = (self._now() - self.start_time).total_seconds()
- item = (elapsed, test_class, test_name)
- if len(self.slow_tests) >= self.num_slow_tests:
- heapq.heappushpop(self.slow_tests, item)
- else:
- heapq.heappush(self.slow_tests, item)
-
- self.results.setdefault(test_class, [])
- self.results[test_class].append((test_name, elapsed) + args)
- self.last_time[test_class] = self._now()
- self.writeTests()
-
- def _writeResult(self, test_name, elapsed, long_result, color,
- short_result, success):
- if self.showAll:
- self.stream.write(' %s' % str(test_name).ljust(66))
- self.colorizer.write(long_result, color)
- if success:
- self._writeElapsedTime(elapsed)
- self.stream.writeln()
- else:
- self.colorizer.write(short_result, color)
-
- def addSuccess(self, test):
- super(NovaTestResult, self).addSuccess(test)
- self._addResult(test, 'OK', 'green', '.', True)
-
- def addFailure(self, test, err):
- if test.id() == 'process-returncode':
- return
- super(NovaTestResult, self).addFailure(test, err)
- self._addResult(test, 'FAIL', 'red', 'F', False)
-
- def addError(self, test, err):
- super(NovaTestResult, self).addFailure(test, err)
- self._addResult(test, 'ERROR', 'red', 'E', False)
-
- def addSkip(self, test, reason=None, details=None):
- super(NovaTestResult, self).addSkip(test, reason, details)
- self._addResult(test, 'SKIP', 'blue', 'S', True)
-
- def startTest(self, test):
- self.start_time = self._now()
- super(NovaTestResult, self).startTest(test)
-
- def writeTestCase(self, cls):
- if not self.results.get(cls):
- return
- if cls != self.last_written:
- self.colorizer.write(cls, 'white')
- self.stream.writeln()
- for result in self.results[cls]:
- self._writeResult(*result)
- del self.results[cls]
- self.stream.flush()
- self.last_written = cls
-
- def writeTests(self):
- time = self.last_time.get(self.last_written, self._now())
- if not self.last_written or (self._now() - time).total_seconds() > 2.0:
- diff = 3.0
- while diff > 2.0:
- classes = self.results.keys()
- oldest = min(classes, key=lambda x: self.last_time[x])
- diff = (self._now() - self.last_time[oldest]).total_seconds()
- self.writeTestCase(oldest)
- else:
- self.writeTestCase(self.last_written)
-
- def done(self):
- self.stopTestRun()
-
- def stopTestRun(self):
- for cls in list(self.results.iterkeys()):
- self.writeTestCase(cls)
- self.stream.writeln()
- self.writeSlowTests()
-
- def writeSlowTests(self):
- # Pare out 'fast' tests
- slow_tests = [item for item in self.slow_tests
- if get_elapsed_time_color(item[0]) != 'green']
- if slow_tests:
- slow_total_time = sum(item[0] for item in slow_tests)
- slow = ("Slowest %i tests took %.2f secs:"
- % (len(slow_tests), slow_total_time))
- self.colorizer.write(slow, 'yellow')
- self.stream.writeln()
- last_cls = None
- # sort by name
- for elapsed, cls, name in sorted(slow_tests,
- key=lambda x: x[1] + x[2]):
- if cls != last_cls:
- self.colorizer.write(cls, 'white')
- self.stream.writeln()
- last_cls = cls
- self.stream.write(' %s' % str(name).ljust(68))
- self._writeElapsedTime(elapsed)
- self.stream.writeln()
-
- def printErrors(self):
- if self.showAll:
- self.stream.writeln()
- self.printErrorList('ERROR', self.errors)
- self.printErrorList('FAIL', self.failures)
-
- def printErrorList(self, flavor, errors):
- for test, err in errors:
- self.colorizer.write("=" * 70, 'red')
- self.stream.writeln()
- self.colorizer.write(flavor, 'red')
- self.stream.writeln(": %s" % test.id())
- self.colorizer.write("-" * 70, 'red')
- self.stream.writeln()
- self.stream.writeln("%s" % err)
-
-
-test = subunit.ProtocolTestCase(sys.stdin, passthrough=None)
-
-if sys.version_info[0:2] <= (2, 6):
- runner = unittest.TextTestRunner(verbosity=2)
-else:
- runner = unittest.TextTestRunner(verbosity=2, resultclass=NovaTestResult)
-
-if runner.run(test).wasSuccessful():
- exit_code = 0
-else:
- exit_code = 1
-sys.exit(exit_code)
diff --git a/tools/use_tempest_lib.sh b/tools/use_tempest_lib.sh
new file mode 100755
index 0000000..ca62c4a
--- /dev/null
+++ b/tools/use_tempest_lib.sh
@@ -0,0 +1,141 @@
+#!/bin/bash
+#
+# Use this script to use interfaces/files from tempest-lib.
+# Many files have been migrated to tempest-lib and tempest has
+# its own copy too.
+# This script helps to remove those from tempest and make use of tempest-lib.
+# It adds the change-id of each file on which they were migrated in lib.
+# This should only be done for files which were migrated to lib with
+# "Migrated" in commit message as done by tempest-lib/tools/migrate_from_tempest.sh script.
+# "Migrated" keyword is used to fetch the migration commit history from lib.
+# To use:
+# 1. Create a new branch in the tempest repo so not to destroy your current
+# working branch
+# 2. Run the script from the repo dir and specify the file paths relative to
+# the root tempest dir(only code and unit tests):
+#
+# tools/use_tempest_lib.sh.sh tempest/file1.py tempest/file2.py
+
+
+function usage {
+ echo "Usage: $0 [OPTION] file1 file2 .."
+ echo "Use files from tempest-lib"
+ echo -e "Input files should be tempest files with path. \n Example- tempest/file1.py tempest/file2.py .."
+ echo ""
+ echo "-s, --service_client Specify if files are service clients."
+ echo "-u, --tempest_lib_git_url Specify the repo to clone tempest-lib from."
+}
+
+function check_all_files_valid {
+ failed=0
+ for file in $files; do
+ # Get the latest change-id for each file
+ latest_commit_id=`git log -n1 -- $file | grep "^commit" | awk '{print $2}'`
+ cd $tmpdir
+ filename=`basename $file`
+ lib_path=`find ./ -name $filename`
+ if [ -z $lib_path ]; then
+ echo "ERROR: $filename does not exist in tempest-lib."
+ failed=$(( failed + 1))
+ cd - > /dev/null
+ continue
+ fi
+ # Get the CHANGE_ID of tempest-lib patch where file was migrated
+ migration_change_id=`git log -n1 --grep "Migrated" -- $lib_path | grep "Change-Id: " | awk '{print $2}'`
+ MIGRATION_IDS=`echo -e "$MIGRATION_IDS\n * $filename: $migration_change_id"`
+ # Get tempest CHANGE_ID of file which was migrated to lib
+ migrated_change_id=`git log -n1 --grep "Migrated" -- $lib_path | grep "* $filename"`
+ migrated_change_id=${migrated_change_id#*:}
+ cd - > /dev/null
+ # Get the commit-id of tempest which was migrated to tempest-lib
+ migrated_commit_id=`git log --grep "$migrated_change_id" -- $file | grep "^commit" | awk '{print $2}'`
+ DIFF=$(git diff $latest_commit_id $migrated_commit_id $file)
+ if [ "$DIFF" != "" ]; then
+ echo "ERROR: $filename in tempest has been updated after migration to tempest-lib. First sync the file to tempest-lib."
+ failed=$(( failed + 1))
+ fi
+ done
+ if [[ $failed -gt 0 ]]; then
+ echo "$failed files had issues"
+ exit $failed
+ fi
+}
+
+set -e
+
+service_client=0
+file_list=''
+
+while [ $# -gt 0 ]; do
+ case "$1" in
+ -h|--help) usage; exit;;
+ -u|--tempest_lib_git_url) tempest_lib_git_url="$2"; shift;;
+ -s|--service_client) service_client=1;;
+ *) files="$files $1";;
+ esac
+ shift
+done
+
+if [ -z "$files" ]; then
+ usage; exit
+fi
+
+TEMPEST_LIB_GIT_URL=${tempest_lib_git_url:-git://git.openstack.org/openstack/tempest-lib}
+
+tmpdir=$(mktemp -d -t use-tempest-lib.XXXX)
+
+# Clone tempest-lib
+git clone $TEMPEST_LIB_GIT_URL $tmpdir
+
+# Checks all provided files are present in lib and
+# not updated in tempest after migration to lib.
+check_all_files_valid
+
+for file in $files; do
+ rm -f $file
+ tempest_dir=`pwd`
+ tempest_dir="$tempest_dir/tempest/"
+ tempest_dirname=`dirname $file`
+ lib_dirname=`echo $tempest_dirname | sed s@tempest\/@tempest_lib/\@`
+ # Convert tempest dirname to import string
+ tempest_import="${tempest_dirname//\//.}"
+ tempest_import=${tempest_import:2:${#tempest_import}}
+ if [ $service_client -eq 1 ]; then
+ # Remove /json path because tempest-lib supports JSON only without XML
+ lib_dirname=`echo $lib_dirname | sed s@\/json@@`
+ fi
+ # Convert tempest-lib dirname to import string
+ tempest_lib_import="${lib_dirname//\//.}"
+ tempest_lib_import=${tempest_lib_import:2:${#tempest_lib_import}}
+ module_name=`basename $file .py`
+ tempest_import1="from $tempest_import.$module_name"
+ tempest_lib_import1="from $tempest_lib_import.$module_name"
+ tempest_import2="from $tempest_import import $module_name"
+ tempest_lib_import2="from $tempest_lib_import import $module_name"
+ set +e
+ grep -rl "$tempest_import1" $tempest_dir | xargs sed -i'' s/"$tempest_import1"/"$tempest_lib_import1"/g 2> /dev/null
+ grep -rl "$tempest_import2" $tempest_dir | xargs sed -i'' s/"$tempest_import2"/"$tempest_lib_import2"/g 2> /dev/null
+ set -e
+ if [[ -z "$file_list" ]]; then
+ file_list="$module_name"
+ else
+ tmp_file_list="$file_list, $module_name"
+ char_size=`echo $tmp_file_list | wc -c`
+ if [ $char_size -lt 27 ]; then
+ file_list="$file_list, $module_name"
+ fi
+ fi
+done
+
+rm -rf $tmpdir
+echo "Completed. Run pep8 and fix error if any"
+
+git add -A tempest/
+# Generate a migration commit
+commit_message="Use $file_list from tempest-lib"
+pre_list=$"The files below have been migrated to tempest-lib\n"
+pre_list=`echo -e $pre_list`
+post_list=$"Now Tempest-lib provides those as stable interfaces. So Tempest should\nstart using those from lib and remove its own copy."
+post_list=`echo -e $post_list`
+
+git commit -m "$commit_message" -m "$pre_list" -m "$MIGRATION_IDS" -m "$post_list"
diff --git a/tox.ini b/tox.ini
index 892cfed..41eece1 100644
--- a/tox.ini
+++ b/tox.ini
@@ -55,7 +55,7 @@
setenv = {[tempestenv]setenv}
deps = {[tempestenv]deps}
# The regex below is used to select which tests to run and exclude the slow tag:
-# See the testrepostiory bug: https://bugs.launchpad.net/testrepository/+bug/1208610
+# See the testrepository bug: https://bugs.launchpad.net/testrepository/+bug/1208610
commands =
find . -type f -name "*.pyc" -delete
bash tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty)) {posargs}'
@@ -65,7 +65,7 @@
setenv = {[tempestenv]setenv}
deps = {[tempestenv]deps}
# The regex below is used to select which tests to run and exclude the slow tag:
-# See the testrepostiory bug: https://bugs.launchpad.net/testrepository/+bug/1208610
+# See the testrepository bug: https://bugs.launchpad.net/testrepository/+bug/1208610
commands =
find . -type f -name "*.pyc" -delete
bash tools/pretty_tox_serial.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty)) {posargs}'
@@ -114,11 +114,11 @@
[testenv:pep8]
commands =
flake8 {posargs}
- python tools/check_uuid.py
+ check-uuid
[testenv:uuidgen]
commands =
- python tools/check_uuid.py --fix
+ check-uuid --fix
[hacking]
local-check-factory = tempest.hacking.checks.factory