Merge "port test_server_metadata and test_server_personality into v3 part1"
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 8742b67..937bbd3 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -220,14 +220,6 @@
# admin credentials are known. (boolean value)
#allow_tenant_isolation=false
-# If allow_tenant_isolation is True and a tenant that would be
-# created for a given test already exists (such as from a
-# previously-failed run), re-use that tenant instead of
-# failing because of the conflict. Note that this would result
-# in the tenant being deleted at the end of a subsequent
-# successful run. (boolean value)
-#allow_tenant_reuse=true
-
# Valid secondary image reference to be used in tests. (string
# value)
#image_ref={$IMAGE_ID}
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 7ef5466..1ba9b16 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -181,17 +181,18 @@
return resp, image
@classmethod
- def rebuild_server(cls, **kwargs):
+ def rebuild_server(cls, server_id, **kwargs):
# Destroy an existing server and creates a new one
- try:
- cls.servers_client.delete_server(cls.server_id)
- cls.servers_client.wait_for_server_termination(cls.server_id)
- except Exception as exc:
- LOG.exception(exc)
- pass
+ if server_id:
+ try:
+ cls.servers_client.delete_server(server_id)
+ cls.servers_client.wait_for_server_termination(server_id)
+ except Exception as exc:
+ LOG.exception(exc)
+ pass
resp, server = cls.create_test_server(wait_until='ACTIVE', **kwargs)
- cls.server_id = server['id']
cls.password = server['adminPass']
+ return server['id']
class BaseV2ComputeAdminTest(BaseV2ComputeTest):
@@ -237,6 +238,7 @@
cls.availability_zone_client = cls.os.availability_zone_v3_client
cls.interfaces_client = cls.os.interfaces_v3_client
cls.hypervisor_client = cls.os.hypervisor_v3_client
+ cls.tenant_usages_client = cls.os.tenant_usages_v3_client
@classmethod
def create_image_from_server(cls, server_id, **kwargs):
@@ -258,17 +260,17 @@
return resp, image
@classmethod
- def rebuild_server(cls, **kwargs):
+ def rebuild_server(cls, server_id, **kwargs):
# Destroy an existing server and creates a new one
try:
- cls.servers_client.delete_server(cls.server_id)
- cls.servers_client.wait_for_server_termination(cls.server_id)
+ cls.servers_client.delete_server(server_id)
+ cls.servers_client.wait_for_server_termination(server_id)
except Exception as exc:
LOG.exception(exc)
pass
resp, server = cls.create_test_server(wait_until='ACTIVE', **kwargs)
- cls.server_id = server['id']
cls.password = server['admin_password']
+ return server['id']
class BaseV3ComputeAdminTest(BaseV3ComputeTest):
@@ -300,3 +302,4 @@
cls.availability_zone_admin_client = \
cls.os_adm.availability_zone_v3_client
cls.hypervisor_admin_client = cls.os_adm.hypervisor_v3_client
+ cls.tenant_usages_admin_client = cls.os_adm.tenant_usages_v3_client
diff --git a/tempest/api/compute/floating_ips/test_floating_ips_actions.py b/tempest/api/compute/floating_ips/test_floating_ips_actions.py
index a06309a..f4ad449 100644
--- a/tempest/api/compute/floating_ips/test_floating_ips_actions.py
+++ b/tempest/api/compute/floating_ips/test_floating_ips_actions.py
@@ -15,10 +15,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-import uuid
-
from tempest.api.compute import base
-from tempest.common.utils import data_utils
+from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
from tempest.test import attr
@@ -32,7 +30,7 @@
def setUpClass(cls):
super(FloatingIPsTestJSON, cls).setUpClass()
cls.client = cls.floating_ips_client
- cls.servers_client = cls.servers_client
+ #cls.servers_client = cls.servers_client
# Server creation
resp, server = cls.create_test_server(wait_until='ACTIVE')
@@ -41,17 +39,6 @@
resp, body = cls.client.create_floating_ip()
cls.floating_ip_id = body['id']
cls.floating_ip = body['ip']
- # Generating a nonexistent floatingIP id
- cls.floating_ip_ids = []
- resp, body = cls.client.list_floating_ips()
- for i in range(len(body)):
- cls.floating_ip_ids.append(body[i]['id'])
- while True:
- cls.non_exist_id = data_utils.rand_int_id(start=999)
- if cls.config.service_available.neutron:
- cls.non_exist_id = str(uuid.uuid4())
- if cls.non_exist_id not in cls.floating_ip_ids:
- break
@classmethod
def tearDownClass(cls):
@@ -76,14 +63,6 @@
# Deleting the floating IP which is created in this method
self.client.delete_floating_ip(floating_ip_id_allocated)
- @attr(type=['negative', 'gate'])
- def test_allocate_floating_ip_from_nonexistent_pool(self):
- # Positive test:Allocation of a new floating IP from a nonexistent_pool
- # to a project should fail
- self.assertRaises(exceptions.NotFound,
- self.client.create_floating_ip,
- "non_exist_pool")
-
@attr(type='gate')
def test_delete_floating_ip(self):
# Positive test:Deletion of valid floating IP from project
@@ -115,38 +94,13 @@
self.server_id)
self.assertEqual(202, resp.status)
- @attr(type=['negative', 'gate'])
- def test_delete_nonexistant_floating_ip(self):
- # Negative test:Deletion of a nonexistent floating IP
- # from project should fail
-
- # Deleting the non existent floating IP
- self.assertRaises(exceptions.NotFound, self.client.delete_floating_ip,
- self.non_exist_id)
-
- @attr(type=['negative', 'gate'])
- def test_associate_nonexistant_floating_ip(self):
- # Negative test:Association of a non existent floating IP
- # to specific server should fail
- # Associating non existent floating IP
- self.assertRaises(exceptions.NotFound,
- self.client.associate_floating_ip_to_server,
- "0.0.0.0", self.server_id)
-
- @attr(type=['negative', 'gate'])
- def test_dissociate_nonexistant_floating_ip(self):
- # Negative test:Dissociation of a non existent floating IP should fail
- # Dissociating non existent floating IP
- self.assertRaises(exceptions.NotFound,
- self.client.disassociate_floating_ip_from_server,
- "0.0.0.0", self.server_id)
-
@attr(type='gate')
def test_associate_already_associated_floating_ip(self):
# positive test:Association of an already associated floating IP
# to specific server should change the association of the Floating IP
# Create server so as to use for Multiple association
- resp, body = self.servers_client.create_server('floating-server2',
+ new_name = rand_name('floating_server')
+ resp, body = self.servers_client.create_server(new_name,
self.image_ref,
self.flavor_ref)
self.servers_client.wait_for_server_status(body['id'], 'ACTIVE')
@@ -173,14 +127,6 @@
self.client.disassociate_floating_ip_from_server,
self.floating_ip, self.server_id)
- @attr(type=['negative', 'gate'])
- def test_associate_ip_to_server_without_passing_floating_ip(self):
- # Negative test:Association of empty floating IP to specific server
- # should raise NotFound exception
- self.assertRaises(exceptions.NotFound,
- self.client.associate_floating_ip_to_server,
- '', self.server_id)
-
class FloatingIPsTestXML(FloatingIPsTestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py b/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py
new file mode 100644
index 0000000..89315bb
--- /dev/null
+++ b/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py
@@ -0,0 +1,94 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# 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.
+
+import uuid
+
+from tempest.api.compute import base
+from tempest.common.utils import data_utils
+from tempest import exceptions
+from tempest.test import attr
+
+
+class FloatingIPsNegativeTestJSON(base.BaseV2ComputeTest):
+ _interface = 'json'
+ server_id = None
+
+ @classmethod
+ def setUpClass(cls):
+ super(FloatingIPsNegativeTestJSON, cls).setUpClass()
+ cls.client = cls.floating_ips_client
+
+ # Server creation
+ resp, server = cls.create_test_server(wait_until='ACTIVE')
+ cls.server_id = server['id']
+ # Generating a nonexistent floatingIP id
+ cls.floating_ip_ids = []
+ resp, body = cls.client.list_floating_ips()
+ for i in range(len(body)):
+ cls.floating_ip_ids.append(body[i]['id'])
+ while True:
+ cls.non_exist_id = data_utils.rand_int_id(start=999)
+ if cls.config.service_available.neutron:
+ cls.non_exist_id = str(uuid.uuid4())
+ if cls.non_exist_id not in cls.floating_ip_ids:
+ break
+
+ @attr(type=['negative', 'gate'])
+ def test_allocate_floating_ip_from_nonexistent_pool(self):
+ # Negative test:Allocation of a new floating IP from a nonexistent_pool
+ # to a project should fail
+ self.assertRaises(exceptions.NotFound,
+ self.client.create_floating_ip,
+ "non_exist_pool")
+
+ @attr(type=['negative', 'gate'])
+ def test_delete_nonexistent_floating_ip(self):
+ # Negative test:Deletion of a nonexistent floating IP
+ # from project should fail
+
+ # Deleting the non existent floating IP
+ self.assertRaises(exceptions.NotFound, self.client.delete_floating_ip,
+ self.non_exist_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_associate_nonexistent_floating_ip(self):
+ # Negative test:Association of a non existent floating IP
+ # to specific server should fail
+ # Associating non existent floating IP
+ self.assertRaises(exceptions.NotFound,
+ self.client.associate_floating_ip_to_server,
+ "0.0.0.0", self.server_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_dissociate_nonexistent_floating_ip(self):
+ # Negative test:Dissociation of a non existent floating IP should fail
+ # Dissociating non existent floating IP
+ self.assertRaises(exceptions.NotFound,
+ self.client.disassociate_floating_ip_from_server,
+ "0.0.0.0", self.server_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_associate_ip_to_server_without_passing_floating_ip(self):
+ # Negative test:Association of empty floating IP to specific server
+ # should raise NotFound exception
+ self.assertRaises(exceptions.NotFound,
+ self.client.associate_floating_ip_to_server,
+ '', self.server_id)
+
+
+class FloatingIPsNegativeTestXML(FloatingIPsNegativeTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/images/test_images_oneserver.py b/tempest/api/compute/images/test_images_oneserver.py
index 612c110..18e32d8 100644
--- a/tempest/api/compute/images/test_images_oneserver.py
+++ b/tempest/api/compute/images/test_images_oneserver.py
@@ -49,7 +49,7 @@
LOG.exception(exc)
# Rebuild server if cannot reach the ACTIVE state
# Usually it means the server had a serius accident
- self.rebuild_server()
+ self.server_id = self.rebuild_server(self.server_id)
@classmethod
def setUpClass(cls):
diff --git a/tempest/api/compute/images/test_images_oneserver_negative.py b/tempest/api/compute/images/test_images_oneserver_negative.py
index b4e778c..4cd41ee 100644
--- a/tempest/api/compute/images/test_images_oneserver_negative.py
+++ b/tempest/api/compute/images/test_images_oneserver_negative.py
@@ -50,7 +50,7 @@
LOG.exception(exc)
# Rebuild server if cannot reach the ACTIVE state
# Usually it means the server had a serius accident
- self.rebuild_server()
+ self.server_id = self.rebuild_server(self.server_id)
@classmethod
def setUpClass(cls):
diff --git a/tempest/api/compute/servers/test_disk_config.py b/tempest/api/compute/servers/test_disk_config.py
index 5e9ee5c..0121c42 100644
--- a/tempest/api/compute/servers/test_disk_config.py
+++ b/tempest/api/compute/servers/test_disk_config.py
@@ -80,7 +80,7 @@
def _get_alternative_flavor(self):
resp, server = self.client.get_server(self.server_id)
- if int(server['flavor']['id']) == self.flavor_ref:
+ if server['flavor']['id'] == self.flavor_ref:
return self.flavor_ref_alt
else:
return self.flavor_ref
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index e3441bd..5fa4c35 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -45,13 +45,13 @@
self.client.wait_for_server_status(self.server_id, 'ACTIVE')
except Exception:
# Rebuild server if something happened to it during a test
- self.rebuild_server()
+ self.server_id = self.rebuild_server(self.server_id)
@classmethod
def setUpClass(cls):
super(ServerActionsTestJSON, cls).setUpClass()
cls.client = cls.servers_client
- cls.rebuild_server()
+ cls.server_id = cls.rebuild_server(None)
@testtools.skipUnless(compute.CHANGE_PASSWORD_AVAILABLE,
'Change password not available.')
diff --git a/tempest/api/compute/servers/test_servers_negative.py b/tempest/api/compute/servers/test_servers_negative.py
index 5ec0cbe..7b86d2d 100644
--- a/tempest/api/compute/servers/test_servers_negative.py
+++ b/tempest/api/compute/servers/test_servers_negative.py
@@ -34,7 +34,7 @@
try:
self.client.wait_for_server_status(self.server_id, 'ACTIVE')
except Exception:
- self.rebuild_server()
+ self.server_id = self.rebuild_server(self.server_id)
@classmethod
def setUpClass(cls):
diff --git a/tempest/api/compute/v3/admin/test_quotas.py b/tempest/api/compute/v3/admin/test_quotas.py
new file mode 100644
index 0000000..f49aae4
--- /dev/null
+++ b/tempest/api/compute/v3/admin/test_quotas.py
@@ -0,0 +1,220 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# 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.api.compute import base
+from tempest.common.utils import data_utils
+from tempest import config
+from tempest import exceptions
+from tempest.test import attr
+from tempest.test import skip_because
+
+
+class QuotasAdminTestJSON(base.BaseV2ComputeAdminTest):
+ _interface = 'json'
+ force_tenant_isolation = True
+
+ @classmethod
+ def setUpClass(cls):
+ super(QuotasAdminTestJSON, cls).setUpClass()
+ cls.auth_url = cls.config.identity.uri
+ cls.client = cls.os.quotas_client
+ cls.adm_client = cls.os_adm.quotas_client
+ cls.identity_admin_client = cls._get_identity_admin_client()
+ cls.sg_client = cls.security_groups_client
+
+ # NOTE(afazekas): these test cases should always create and use a new
+ # tenant most of them should be skipped if we can't do that
+ cls.demo_tenant_id = cls.isolated_creds.get_primary_user().get(
+ 'tenantId')
+
+ cls.default_quota_set = set(('injected_file_content_bytes',
+ 'metadata_items', 'injected_files',
+ 'ram', 'floating_ips',
+ 'fixed_ips', 'key_pairs',
+ 'injected_file_path_bytes',
+ 'instances', 'security_group_rules',
+ 'cores', 'security_groups'))
+
+ @attr(type='smoke')
+ def test_get_default_quotas(self):
+ # Admin can get the default resource quota set for a tenant
+ expected_quota_set = self.default_quota_set | set(['id'])
+ resp, quota_set = self.client.get_default_quota_set(
+ self.demo_tenant_id)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(sorted(expected_quota_set),
+ sorted(quota_set.keys()))
+ self.assertEqual(quota_set['id'], self.demo_tenant_id)
+
+ @attr(type='gate')
+ def test_update_all_quota_resources_for_tenant(self):
+ # Admin can update all the resource quota limits for a tenant
+ resp, default_quota_set = self.client.get_default_quota_set(
+ self.demo_tenant_id)
+ new_quota_set = {'injected_file_content_bytes': 20480,
+ 'metadata_items': 256, 'injected_files': 10,
+ 'ram': 10240, 'floating_ips': 20, 'fixed_ips': 10,
+ 'key_pairs': 200, 'injected_file_path_bytes': 512,
+ 'instances': 20, 'security_group_rules': 20,
+ 'cores': 2, 'security_groups': 20}
+ # Update limits for all quota resources
+ resp, quota_set = self.adm_client.update_quota_set(
+ self.demo_tenant_id,
+ force=True,
+ **new_quota_set)
+
+ default_quota_set.pop('id')
+ self.addCleanup(self.adm_client.update_quota_set,
+ self.demo_tenant_id, **default_quota_set)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(new_quota_set, quota_set)
+
+ # TODO(afazekas): merge these test cases
+ @attr(type='gate')
+ def test_get_updated_quotas(self):
+ # Verify that GET shows the updated quota set
+ tenant_name = data_utils.rand_name('cpu_quota_tenant_')
+ tenant_desc = tenant_name + '-desc'
+ identity_client = self.os_adm.identity_client
+ _, tenant = identity_client.create_tenant(name=tenant_name,
+ description=tenant_desc)
+ tenant_id = tenant['id']
+ self.addCleanup(identity_client.delete_tenant,
+ tenant_id)
+
+ self.adm_client.update_quota_set(tenant_id,
+ ram='5120')
+ resp, quota_set = self.adm_client.get_quota_set(tenant_id)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(quota_set['ram'], 5120)
+
+ # TODO(afazekas): Add dedicated tenant to the skiped quota tests
+ # it can be moved into the setUpClass as well
+ @attr(type='gate')
+ def test_create_server_when_cpu_quota_is_full(self):
+ # Disallow server creation when tenant's vcpu quota is full
+ resp, quota_set = self.client.get_quota_set(self.demo_tenant_id)
+ default_vcpu_quota = quota_set['cores']
+ vcpu_quota = 0 # Set the quota to zero to conserve resources
+
+ resp, quota_set = self.adm_client.update_quota_set(self.demo_tenant_id,
+ force=True,
+ cores=vcpu_quota)
+
+ self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id,
+ cores=default_vcpu_quota)
+ self.assertRaises(exceptions.OverLimit, self.create_test_server)
+
+ @attr(type='gate')
+ def test_create_server_when_memory_quota_is_full(self):
+ # Disallow server creation when tenant's memory quota is full
+ resp, quota_set = self.client.get_quota_set(self.demo_tenant_id)
+ default_mem_quota = quota_set['ram']
+ mem_quota = 0 # Set the quota to zero to conserve resources
+
+ self.adm_client.update_quota_set(self.demo_tenant_id,
+ force=True,
+ ram=mem_quota)
+
+ self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id,
+ ram=default_mem_quota)
+ self.assertRaises(exceptions.OverLimit, self.create_test_server)
+
+ @attr(type='gate')
+ def test_update_quota_normal_user(self):
+ self.assertRaises(exceptions.Unauthorized,
+ self.client.update_quota_set,
+ self.demo_tenant_id,
+ ram=0)
+
+ @attr(type=['negative', 'gate'])
+ def test_create_server_when_instances_quota_is_full(self):
+ # Once instances quota limit is reached, disallow server creation
+ resp, quota_set = self.client.get_quota_set(self.demo_tenant_id)
+ default_instances_quota = quota_set['instances']
+ instances_quota = 0 # Set quota to zero to disallow server creation
+
+ self.adm_client.update_quota_set(self.demo_tenant_id,
+ force=True,
+ instances=instances_quota)
+ self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id,
+ instances=default_instances_quota)
+ self.assertRaises(exceptions.OverLimit, self.create_test_server)
+
+ @skip_because(bug="1186354",
+ condition=config.TempestConfig().service_available.neutron)
+ @attr(type=['negative', 'gate'])
+ def test_security_groups_exceed_limit(self):
+ # Negative test: Creation Security Groups over limit should FAIL
+
+ resp, quota_set = self.client.get_quota_set(self.demo_tenant_id)
+ default_sg_quota = quota_set['security_groups']
+ sg_quota = 0 # Set the quota to zero to conserve resources
+
+ resp, quota_set =\
+ self.adm_client.update_quota_set(self.demo_tenant_id,
+ force=True,
+ security_groups=sg_quota)
+
+ self.addCleanup(self.adm_client.update_quota_set,
+ self.demo_tenant_id,
+ security_groups=default_sg_quota)
+
+ # Check we cannot create anymore
+ self.assertRaises(exceptions.OverLimit,
+ self.sg_client.create_security_group,
+ "sg-overlimit", "sg-desc")
+
+ @skip_because(bug="1186354",
+ condition=config.TempestConfig().service_available.neutron)
+ @attr(type=['negative', 'gate'])
+ def test_security_groups_rules_exceed_limit(self):
+ # Negative test: Creation of Security Group Rules should FAIL
+ # when we reach limit maxSecurityGroupRules
+
+ resp, quota_set = self.client.get_quota_set(self.demo_tenant_id)
+ default_sg_rules_quota = quota_set['security_group_rules']
+ sg_rules_quota = 0 # Set the quota to zero to conserve resources
+
+ resp, quota_set =\
+ self.adm_client.update_quota_set(
+ self.demo_tenant_id,
+ force=True,
+ security_group_rules=sg_rules_quota)
+
+ self.addCleanup(self.adm_client.update_quota_set,
+ self.demo_tenant_id,
+ security_group_rules=default_sg_rules_quota)
+
+ s_name = data_utils.rand_name('securitygroup-')
+ s_description = data_utils.rand_name('description-')
+ resp, securitygroup =\
+ self.sg_client.create_security_group(s_name, s_description)
+ self.addCleanup(self.sg_client.delete_security_group,
+ securitygroup['id'])
+
+ secgroup_id = securitygroup['id']
+ ip_protocol = 'tcp'
+
+ # Check we cannot create SG rule anymore
+ self.assertRaises(exceptions.OverLimit,
+ self.sg_client.create_security_group_rule,
+ secgroup_id, ip_protocol, 1025, 1025)
+
+
+class QuotasAdminTestXML(QuotasAdminTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/v3/admin/test_simple_tenant_usage.py b/tempest/api/compute/v3/admin/test_simple_tenant_usage.py
index a599f06..3fc58eb 100644
--- a/tempest/api/compute/v3/admin/test_simple_tenant_usage.py
+++ b/tempest/api/compute/v3/admin/test_simple_tenant_usage.py
@@ -23,15 +23,15 @@
import time
-class TenantUsagesTestJSON(base.BaseV2ComputeAdminTest):
+class TenantUsagesV3TestJSON(base.BaseV3ComputeAdminTest):
_interface = 'json'
@classmethod
def setUpClass(cls):
- super(TenantUsagesTestJSON, cls).setUpClass()
- cls.adm_client = cls.os_adm.tenant_usages_client
- cls.client = cls.os.tenant_usages_client
+ super(TenantUsagesV3TestJSON, cls).setUpClass()
+ cls.adm_client = cls.tenant_usages_admin_client
+ cls.client = cls.tenant_usages_client
cls.identity_client = cls._get_identity_admin_client()
resp, tenants = cls.identity_client.list_tenants()
@@ -111,5 +111,5 @@
self.client.list_tenant_usages, params)
-class TenantUsagesTestXML(TenantUsagesTestJSON):
+class TenantUsagesV3TestXML(TenantUsagesV3TestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/v3/servers/test_create_server.py b/tempest/api/compute/v3/servers/test_create_server.py
new file mode 100644
index 0000000..24ade96
--- /dev/null
+++ b/tempest/api/compute/v3/servers/test_create_server.py
@@ -0,0 +1,130 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# 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.
+
+import base64
+
+import netaddr
+import testtools
+
+from tempest.api import compute
+from tempest.api.compute import base
+from tempest.common.utils import data_utils
+from tempest.common.utils.linux.remote_client import RemoteClient
+import tempest.config
+from tempest.test import attr
+
+
+class ServersTestJSON(base.BaseV2ComputeTest):
+ _interface = 'json'
+ run_ssh = tempest.config.TempestConfig().compute.run_ssh
+ disk_config = 'AUTO'
+
+ @classmethod
+ def setUpClass(cls):
+ super(ServersTestJSON, cls).setUpClass()
+ cls.meta = {'hello': 'world'}
+ cls.accessIPv4 = '1.1.1.1'
+ cls.accessIPv6 = '0000:0000:0000:0000:0000:babe:220.12.22.2'
+ cls.name = data_utils.rand_name('server')
+ file_contents = 'This is a test file.'
+ personality = [{'path': '/test.txt',
+ 'contents': base64.b64encode(file_contents)}]
+ cls.client = cls.servers_client
+ cli_resp = cls.create_test_server(name=cls.name,
+ meta=cls.meta,
+ accessIPv4=cls.accessIPv4,
+ accessIPv6=cls.accessIPv6,
+ personality=personality,
+ disk_config=cls.disk_config)
+ cls.resp, cls.server_initial = cli_resp
+ cls.password = cls.server_initial['adminPass']
+ cls.client.wait_for_server_status(cls.server_initial['id'], 'ACTIVE')
+ resp, cls.server = cls.client.get_server(cls.server_initial['id'])
+
+ @attr(type='smoke')
+ def test_create_server_response(self):
+ # Check that the required fields are returned with values
+ self.assertEqual(202, self.resp.status)
+ self.assertTrue(self.server_initial['id'] is not None)
+ self.assertTrue(self.server_initial['adminPass'] is not None)
+
+ @attr(type='smoke')
+ def test_verify_server_details(self):
+ # Verify the specified server attributes are set correctly
+ self.assertEqual(self.accessIPv4, self.server['accessIPv4'])
+ # NOTE(maurosr): See http://tools.ietf.org/html/rfc5952 (section 4)
+ # Here we compare directly with the canonicalized format.
+ self.assertEqual(self.server['accessIPv6'],
+ str(netaddr.IPAddress(self.accessIPv6)))
+ self.assertEqual(self.name, self.server['name'])
+ self.assertEqual(self.image_ref, self.server['image']['id'])
+ self.assertEqual(self.flavor_ref, self.server['flavor']['id'])
+ self.assertEqual(self.meta, self.server['metadata'])
+
+ @attr(type='smoke')
+ def test_list_servers(self):
+ # The created server should be in the list of all servers
+ resp, body = self.client.list_servers()
+ servers = body['servers']
+ found = any([i for i in servers if i['id'] == self.server['id']])
+ self.assertTrue(found)
+
+ @attr(type='smoke')
+ def test_list_servers_with_detail(self):
+ # The created server should be in the detailed list of all servers
+ resp, body = self.client.list_servers_with_detail()
+ servers = body['servers']
+ found = any([i for i in servers if i['id'] == self.server['id']])
+ self.assertTrue(found)
+
+ @testtools.skipIf(not run_ssh, 'Instance validation tests are disabled.')
+ @attr(type='gate')
+ def test_can_log_into_created_server(self):
+ # Check that the user can authenticate with the generated password
+ linux_client = RemoteClient(self.server, self.ssh_user, self.password)
+ self.assertTrue(linux_client.can_authenticate())
+
+ @testtools.skipIf(not run_ssh, 'Instance validation tests are disabled.')
+ @attr(type='gate')
+ def test_verify_created_server_vcpus(self):
+ # Verify that the number of vcpus reported by the instance matches
+ # the amount stated by the flavor
+ resp, flavor = self.flavors_client.get_flavor_details(self.flavor_ref)
+ linux_client = RemoteClient(self.server, self.ssh_user, self.password)
+ self.assertEqual(flavor['vcpus'], linux_client.get_number_of_vcpus())
+
+ @testtools.skipIf(not run_ssh, 'Instance validation tests are disabled.')
+ @attr(type='gate')
+ def test_host_name_is_same_as_server_name(self):
+ # Verify the instance host name is the same as the server name
+ linux_client = RemoteClient(self.server, self.ssh_user, self.password)
+ self.assertTrue(linux_client.hostname_equals_servername(self.name))
+
+
+class ServersTestManualDisk(ServersTestJSON):
+ disk_config = 'MANUAL'
+
+ @classmethod
+ def setUpClass(cls):
+ if not compute.DISK_CONFIG_ENABLED:
+ msg = "DiskConfig extension not enabled."
+ raise cls.skipException(msg)
+ super(ServersTestManualDisk, cls).setUpClass()
+
+
+class ServersTestXML(ServersTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/v3/servers/test_multiple_create.py b/tempest/api/compute/v3/servers/test_multiple_create.py
new file mode 100644
index 0000000..080bd1a
--- /dev/null
+++ b/tempest/api/compute/v3/servers/test_multiple_create.py
@@ -0,0 +1,95 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM Corp
+# 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.api.compute import base
+from tempest.common.utils import data_utils
+from tempest import exceptions
+from tempest.test import attr
+
+
+class MultipleCreateTestJSON(base.BaseV2ComputeTest):
+ _interface = 'json'
+ _name = 'multiple-create-test'
+
+ def _generate_name(self):
+ return data_utils.rand_name(self._name)
+
+ def _create_multiple_servers(self, name=None, wait_until=None, **kwargs):
+ """
+ This is the right way to create_multiple servers and manage to get the
+ created servers into the servers list to be cleaned up after all.
+ """
+ kwargs['name'] = kwargs.get('name', self._generate_name())
+ resp, body = self.create_test_server(**kwargs)
+
+ return resp, body
+
+ @attr(type='gate')
+ def test_multiple_create(self):
+ resp, body = self._create_multiple_servers(wait_until='ACTIVE',
+ min_count=1,
+ max_count=2)
+ # NOTE(maurosr): do status response check and also make sure that
+ # reservation_id is not in the response body when the request send
+ # contains return_reservation_id=False
+ self.assertEqual('202', resp['status'])
+ self.assertNotIn('reservation_id', body)
+
+ @attr(type=['negative', 'gate'])
+ def test_min_count_less_than_one(self):
+ invalid_min_count = 0
+ self.assertRaises(exceptions.BadRequest, self._create_multiple_servers,
+ min_count=invalid_min_count)
+
+ @attr(type=['negative', 'gate'])
+ def test_min_count_non_integer(self):
+ invalid_min_count = 2.5
+ self.assertRaises(exceptions.BadRequest, self._create_multiple_servers,
+ min_count=invalid_min_count)
+
+ @attr(type=['negative', 'gate'])
+ def test_max_count_less_than_one(self):
+ invalid_max_count = 0
+ self.assertRaises(exceptions.BadRequest, self._create_multiple_servers,
+ max_count=invalid_max_count)
+
+ @attr(type=['negative', 'gate'])
+ def test_max_count_non_integer(self):
+ invalid_max_count = 2.5
+ self.assertRaises(exceptions.BadRequest, self._create_multiple_servers,
+ max_count=invalid_max_count)
+
+ @attr(type=['negative', 'gate'])
+ def test_max_count_less_than_min_count(self):
+ min_count = 3
+ max_count = 2
+ self.assertRaises(exceptions.BadRequest, self._create_multiple_servers,
+ min_count=min_count,
+ max_count=max_count)
+
+ @attr(type='gate')
+ def test_multiple_create_with_reservation_return(self):
+ resp, body = self._create_multiple_servers(wait_until='ACTIVE',
+ min_count=1,
+ max_count=2,
+ return_reservation_id=True)
+ self.assertEqual(resp['status'], '202')
+ self.assertIn('reservation_id', body)
+
+
+class MultipleCreateTestXML(MultipleCreateTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/v3/servers/test_server_actions.py b/tempest/api/compute/v3/servers/test_server_actions.py
index 20cdf30..dc78a47 100644
--- a/tempest/api/compute/v3/servers/test_server_actions.py
+++ b/tempest/api/compute/v3/servers/test_server_actions.py
@@ -45,13 +45,13 @@
self.client.wait_for_server_status(self.server_id, 'ACTIVE')
except Exception:
# Rebuild server if something happened to it during a test
- self.rebuild_server()
+ self.server_id = self.rebuild_server(self.server_id)
@classmethod
def setUpClass(cls):
super(ServerActionsV3TestJSON, cls).setUpClass()
cls.client = cls.servers_client
- cls.rebuild_server()
+ cls.server_id = cls.rebuild_server(None)
@testtools.skipUnless(compute.CHANGE_PASSWORD_AVAILABLE,
'Change password not available.')
diff --git a/tempest/api/compute/v3/servers/test_server_rescue.py b/tempest/api/compute/v3/servers/test_server_rescue.py
new file mode 100644
index 0000000..1008670
--- /dev/null
+++ b/tempest/api/compute/v3/servers/test_server_rescue.py
@@ -0,0 +1,233 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 Hewlett-Packard Development Company, L.P.
+# 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.api.compute import base
+from tempest.common.utils import data_utils
+from tempest import exceptions
+from tempest.test import attr
+
+
+class ServerRescueTestJSON(base.BaseV2ComputeTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(ServerRescueTestJSON, cls).setUpClass()
+ cls.device = 'vdf'
+
+ # Floating IP creation
+ resp, body = cls.floating_ips_client.create_floating_ip()
+ cls.floating_ip_id = str(body['id']).strip()
+ cls.floating_ip = str(body['ip']).strip()
+
+ # Security group creation
+ cls.sg_name = data_utils.rand_name('sg')
+ cls.sg_desc = data_utils.rand_name('sg-desc')
+ resp, cls.sg = \
+ cls.security_groups_client.create_security_group(cls.sg_name,
+ cls.sg_desc)
+ cls.sg_id = cls.sg['id']
+
+ # Create a volume and wait for it to become ready for attach
+ resp, cls.volume_to_attach = \
+ cls.volumes_extensions_client.create_volume(1,
+ display_name=
+ 'test_attach')
+ cls.volumes_extensions_client.wait_for_volume_status(
+ cls.volume_to_attach['id'], 'available')
+
+ # Create a volume and wait for it to become ready for attach
+ resp, cls.volume_to_detach = \
+ cls.volumes_extensions_client.create_volume(1,
+ display_name=
+ 'test_detach')
+ cls.volumes_extensions_client.wait_for_volume_status(
+ cls.volume_to_detach['id'], 'available')
+
+ # Server for positive tests
+ resp, server = cls.create_test_server(wait_until='BUILD')
+ resp, resc_server = cls.create_test_server(wait_until='ACTIVE')
+ cls.server_id = server['id']
+ cls.password = server['adminPass']
+ cls.servers_client.wait_for_server_status(cls.server_id, 'ACTIVE')
+
+ # Server for negative tests
+ cls.rescue_id = resc_server['id']
+ cls.rescue_password = resc_server['adminPass']
+
+ cls.servers_client.rescue_server(
+ cls.rescue_id, cls.rescue_password)
+ cls.servers_client.wait_for_server_status(cls.rescue_id, 'RESCUE')
+
+ def setUp(self):
+ super(ServerRescueTestJSON, self).setUp()
+
+ @classmethod
+ def tearDownClass(cls):
+ # Deleting the floating IP which is created in this method
+ cls.floating_ips_client.delete_floating_ip(cls.floating_ip_id)
+ client = cls.volumes_extensions_client
+ client.delete_volume(str(cls.volume_to_attach['id']).strip())
+ client.delete_volume(str(cls.volume_to_detach['id']).strip())
+ resp, cls.sg = cls.security_groups_client.delete_security_group(
+ cls.sg_id)
+ super(ServerRescueTestJSON, cls).tearDownClass()
+
+ def tearDown(self):
+ super(ServerRescueTestJSON, self).tearDown()
+
+ def _detach(self, server_id, volume_id):
+ self.servers_client.detach_volume(server_id, volume_id)
+ self.volumes_extensions_client.wait_for_volume_status(volume_id,
+ 'available')
+
+ def _delete(self, volume_id):
+ self.volumes_extensions_client.delete_volume(volume_id)
+
+ def _unrescue(self, server_id):
+ resp, body = self.servers_client.unrescue_server(server_id)
+ self.assertEqual(202, resp.status)
+ self.servers_client.wait_for_server_status(server_id, 'ACTIVE')
+
+ def _unpause(self, server_id):
+ resp, body = self.servers_client.unpause_server(server_id)
+ self.assertEqual(202, resp.status)
+ self.servers_client.wait_for_server_status(server_id, 'ACTIVE')
+
+ @attr(type='smoke')
+ def test_rescue_unrescue_instance(self):
+ resp, body = self.servers_client.rescue_server(
+ self.server_id, self.password)
+ self.assertEqual(200, resp.status)
+ self.servers_client.wait_for_server_status(self.server_id, 'RESCUE')
+ resp, body = self.servers_client.unrescue_server(self.server_id)
+ self.assertEqual(202, resp.status)
+ self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+ @attr(type=['negative', 'gate'])
+ def test_rescue_paused_instance(self):
+ # Rescue a paused server
+ resp, body = self.servers_client.pause_server(
+ self.server_id)
+ self.addCleanup(self._unpause, self.server_id)
+ self.assertEqual(202, resp.status)
+ self.servers_client.wait_for_server_status(self.server_id, 'PAUSED')
+ self.assertRaises(exceptions.Conflict,
+ self.servers_client.rescue_server,
+ self.server_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_rescued_vm_reboot(self):
+ self.assertRaises(exceptions.Conflict, self.servers_client.reboot,
+ self.rescue_id, 'HARD')
+
+ @attr(type=['negative', 'gate'])
+ def test_rescue_non_existent_server(self):
+ # Rescue a non-existing server
+ self.assertRaises(exceptions.NotFound,
+ self.servers_client.rescue_server,
+ '999erra43')
+
+ @attr(type=['negative', 'gate'])
+ def test_rescued_vm_rebuild(self):
+ self.assertRaises(exceptions.Conflict,
+ self.servers_client.rebuild,
+ self.rescue_id,
+ self.image_ref_alt)
+
+ @attr(type=['negative', 'gate'])
+ def test_rescued_vm_attach_volume(self):
+ # Rescue the server
+ self.servers_client.rescue_server(self.server_id, self.password)
+ self.servers_client.wait_for_server_status(self.server_id, 'RESCUE')
+ self.addCleanup(self._unrescue, self.server_id)
+
+ # Attach the volume to the server
+ self.assertRaises(exceptions.Conflict,
+ self.servers_client.attach_volume,
+ self.server_id,
+ self.volume_to_attach['id'],
+ device='/dev/%s' % self.device)
+
+ @attr(type=['negative', 'gate'])
+ def test_rescued_vm_detach_volume(self):
+ # Attach the volume to the server
+ self.servers_client.attach_volume(self.server_id,
+ self.volume_to_detach['id'],
+ device='/dev/%s' % self.device)
+ self.volumes_extensions_client.wait_for_volume_status(
+ self.volume_to_detach['id'], 'in-use')
+
+ # Rescue the server
+ self.servers_client.rescue_server(self.server_id, self.password)
+ self.servers_client.wait_for_server_status(self.server_id, 'RESCUE')
+ # addCleanup is a LIFO queue
+ self.addCleanup(self._detach, self.server_id,
+ self.volume_to_detach['id'])
+ self.addCleanup(self._unrescue, self.server_id)
+
+ # Detach the volume from the server expecting failure
+ self.assertRaises(exceptions.Conflict,
+ self.servers_client.detach_volume,
+ self.server_id,
+ self.volume_to_detach['id'])
+
+ @attr(type='gate')
+ def test_rescued_vm_associate_dissociate_floating_ip(self):
+ # Rescue the server
+ self.servers_client.rescue_server(
+ self.server_id, self.password)
+ self.servers_client.wait_for_server_status(self.server_id, 'RESCUE')
+ self.addCleanup(self._unrescue, self.server_id)
+
+ # Association of floating IP to a rescued vm
+ client = self.floating_ips_client
+ resp, body = client.associate_floating_ip_to_server(self.floating_ip,
+ self.server_id)
+ self.assertEqual(202, resp.status)
+
+ # Disassociation of floating IP that was associated in this method
+ resp, body = \
+ client.disassociate_floating_ip_from_server(self.floating_ip,
+ self.server_id)
+ self.assertEqual(202, resp.status)
+
+ @attr(type='gate')
+ def test_rescued_vm_add_remove_security_group(self):
+ # Rescue the server
+ self.servers_client.rescue_server(
+ self.server_id, self.password)
+ self.servers_client.wait_for_server_status(self.server_id, 'RESCUE')
+
+ # Add Security group
+ resp, body = self.servers_client.add_security_group(self.server_id,
+ self.sg_name)
+ self.assertEqual(202, resp.status)
+
+ # Delete Security group
+ resp, body = self.servers_client.remove_security_group(self.server_id,
+ self.sg_name)
+ self.assertEqual(202, resp.status)
+
+ # Unrescue the server
+ resp, body = self.servers_client.unrescue_server(self.server_id)
+ self.assertEqual(202, resp.status)
+ self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+
+class ServerRescueTestXML(ServerRescueTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/v3/servers/test_servers.py b/tempest/api/compute/v3/servers/test_servers.py
new file mode 100644
index 0000000..d72476d
--- /dev/null
+++ b/tempest/api/compute/v3/servers/test_servers.py
@@ -0,0 +1,133 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# 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.api.compute import base
+from tempest.common.utils import data_utils
+from tempest.test import attr
+
+
+class ServersTestJSON(base.BaseV2ComputeTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(ServersTestJSON, cls).setUpClass()
+ cls.client = cls.servers_client
+
+ def tearDown(self):
+ self.clear_servers()
+ super(ServersTestJSON, self).tearDown()
+
+ @attr(type='gate')
+ def test_create_server_with_admin_password(self):
+ # If an admin password is provided on server creation, the server's
+ # root password should be set to that password.
+ resp, server = self.create_test_server(adminPass='testpassword')
+
+ # Verify the password is set correctly in the response
+ self.assertEqual('testpassword', server['adminPass'])
+
+ @attr(type='gate')
+ def test_create_with_existing_server_name(self):
+ # Creating a server with a name that already exists is allowed
+
+ # TODO(sdague): clear out try, we do cleanup one layer up
+ server_name = data_utils.rand_name('server')
+ resp, server = self.create_test_server(name=server_name,
+ wait_until='ACTIVE')
+ id1 = server['id']
+ resp, server = self.create_test_server(name=server_name,
+ wait_until='ACTIVE')
+ id2 = server['id']
+ self.assertNotEqual(id1, id2, "Did not create a new server")
+ resp, server = self.client.get_server(id1)
+ name1 = server['name']
+ resp, server = self.client.get_server(id2)
+ name2 = server['name']
+ self.assertEqual(name1, name2)
+
+ @attr(type='gate')
+ def test_create_specify_keypair(self):
+ # Specify a keypair while creating a server
+
+ key_name = data_utils.rand_name('key')
+ resp, keypair = self.keypairs_client.create_keypair(key_name)
+ resp, body = self.keypairs_client.list_keypairs()
+ resp, server = self.create_test_server(key_name=key_name)
+ self.assertEqual('202', resp['status'])
+ self.client.wait_for_server_status(server['id'], 'ACTIVE')
+ resp, server = self.client.get_server(server['id'])
+ self.assertEqual(key_name, server['key_name'])
+
+ @attr(type='gate')
+ def test_update_server_name(self):
+ # The server name should be changed to the the provided value
+ resp, server = self.create_test_server(wait_until='ACTIVE')
+
+ # Update the server with a new name
+ resp, server = self.client.update_server(server['id'],
+ name='newname')
+ self.assertEqual(200, resp.status)
+ self.client.wait_for_server_status(server['id'], 'ACTIVE')
+
+ # Verify the name of the server has changed
+ resp, server = self.client.get_server(server['id'])
+ self.assertEqual('newname', server['name'])
+
+ @attr(type='gate')
+ def test_update_access_server_address(self):
+ # The server's access addresses should reflect the provided values
+ resp, server = self.create_test_server(wait_until='ACTIVE')
+
+ # Update the IPv4 and IPv6 access addresses
+ resp, body = self.client.update_server(server['id'],
+ accessIPv4='1.1.1.1',
+ accessIPv6='::babe:202:202')
+ self.assertEqual(200, resp.status)
+ self.client.wait_for_server_status(server['id'], 'ACTIVE')
+
+ # Verify the access addresses have been updated
+ resp, server = self.client.get_server(server['id'])
+ self.assertEqual('1.1.1.1', server['accessIPv4'])
+ self.assertEqual('::babe:202:202', server['accessIPv6'])
+
+ @attr(type='gate')
+ def test_delete_server_while_in_building_state(self):
+ # Delete a server while it's VM state is Building
+ resp, server = self.create_test_server(wait_until='BUILD')
+ resp, _ = self.client.delete_server(server['id'])
+ self.assertEqual('204', resp['status'])
+
+ @attr(type='gate')
+ def test_delete_active_server(self):
+ # Delete a server while it's VM state is Active
+ resp, server = self.create_test_server(wait_until='ACTIVE')
+ resp, _ = self.client.delete_server(server['id'])
+ self.assertEqual('204', resp['status'])
+
+ @attr(type='gate')
+ def test_create_server_with_ipv6_addr_only(self):
+ # Create a server without an IPv4 address(only IPv6 address).
+ resp, server = self.create_test_server(accessIPv6='2001:2001::3')
+ self.assertEqual('202', resp['status'])
+ self.client.wait_for_server_status(server['id'], 'ACTIVE')
+ resp, server = self.client.get_server(server['id'])
+ self.assertEqual('2001:2001::3', server['accessIPv6'])
+
+
+class ServersTestXML(ServersTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/v3/servers/test_servers_negative.py b/tempest/api/compute/v3/servers/test_servers_negative.py
index 5ec0cbe..7b86d2d 100644
--- a/tempest/api/compute/v3/servers/test_servers_negative.py
+++ b/tempest/api/compute/v3/servers/test_servers_negative.py
@@ -34,7 +34,7 @@
try:
self.client.wait_for_server_status(self.server_id, 'ACTIVE')
except Exception:
- self.rebuild_server()
+ self.server_id = self.rebuild_server(self.server_id)
@classmethod
def setUpClass(cls):
diff --git a/tempest/api/compute/v3/test_quotas.py b/tempest/api/compute/v3/test_quotas.py
new file mode 100644
index 0000000..475d055
--- /dev/null
+++ b/tempest/api/compute/v3/test_quotas.py
@@ -0,0 +1,73 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# 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.api.compute import base
+from tempest.test import attr
+
+
+class QuotasTestJSON(base.BaseV2ComputeTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(QuotasTestJSON, cls).setUpClass()
+ cls.client = cls.quotas_client
+ cls.admin_client = cls._get_identity_admin_client()
+ resp, tenants = cls.admin_client.list_tenants()
+ cls.tenant_id = [tnt['id'] for tnt in tenants if tnt['name'] ==
+ cls.client.tenant_name][0]
+ cls.default_quota_set = set(('injected_file_content_bytes',
+ 'metadata_items', 'injected_files',
+ 'ram', 'floating_ips',
+ 'fixed_ips', 'key_pairs',
+ 'injected_file_path_bytes',
+ 'instances', 'security_group_rules',
+ 'cores', 'security_groups'))
+
+ @attr(type='smoke')
+ def test_get_quotas(self):
+ # User can get the quota set for it's tenant
+ expected_quota_set = self.default_quota_set | set(['id'])
+ resp, quota_set = self.client.get_quota_set(self.tenant_id)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(sorted(expected_quota_set),
+ sorted(quota_set.keys()))
+ self.assertEqual(quota_set['id'], self.tenant_id)
+
+ @attr(type='smoke')
+ def test_get_default_quotas(self):
+ # User can get the default quota set for it's tenant
+ expected_quota_set = self.default_quota_set | set(['id'])
+ resp, quota_set = self.client.get_default_quota_set(self.tenant_id)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(sorted(expected_quota_set),
+ sorted(quota_set.keys()))
+ self.assertEqual(quota_set['id'], self.tenant_id)
+
+ @attr(type='smoke')
+ def test_compare_tenant_quotas_with_default_quotas(self):
+ # Tenants are created with the default quota values
+ resp, defualt_quota_set = \
+ self.client.get_default_quota_set(self.tenant_id)
+ self.assertEqual(200, resp.status)
+ resp, tenant_quota_set = self.client.get_quota_set(self.tenant_id)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(defualt_quota_set, tenant_quota_set)
+
+
+class QuotasTestXML(QuotasTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/network/admin/test_dhcp_agent_scheduler.py b/tempest/api/network/admin/test_dhcp_agent_scheduler.py
new file mode 100644
index 0000000..ac10e68
--- /dev/null
+++ b/tempest/api/network/admin/test_dhcp_agent_scheduler.py
@@ -0,0 +1,78 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM Corp.
+#
+# 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.api.network import base
+from tempest.test import attr
+
+
+class DHCPAgentSchedulersTestJSON(base.BaseAdminNetworkTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(DHCPAgentSchedulersTestJSON, cls).setUpClass()
+ # Create a network and make sure it will be hosted by a
+ # dhcp agent.
+ cls.network = cls.create_network()
+ cls.subnet = cls.create_subnet(cls.network)
+ cls.cidr = cls.subnet['cidr']
+ cls.port = cls.create_port(cls.network)
+
+ @attr(type='smoke')
+ def test_list_dhcp_agent_hosting_network(self):
+ resp, body = self.admin_client.list_dhcp_agent_hosting_network(
+ self.network['id'])
+ self.assertEqual(resp['status'], '200')
+
+ @attr(type='smoke')
+ def test_list_networks_hosted_by_one_dhcp(self):
+ resp, body = self.admin_client.list_dhcp_agent_hosting_network(
+ self.network['id'])
+ agents = body['agents']
+ self.assertIsNotNone(agents)
+ agent = agents[0]
+ self.assertTrue(self._check_network_in_dhcp_agent(
+ self.network['id'], agent))
+
+ def _check_network_in_dhcp_agent(self, network_id, agent):
+ network_ids = []
+ resp, body = self.admin_client.list_networks_hosted_by_one_dhcp_agent(
+ agent['id'])
+ self.assertEqual(resp['status'], '200')
+ networks = body['networks']
+ for network in networks:
+ network_ids.append(network['id'])
+ return network_id in network_ids
+
+ @attr(type='smoke')
+ def test_remove_network_from_dhcp_agent(self):
+ resp, body = self.admin_client.list_dhcp_agent_hosting_network(
+ self.network['id'])
+ agents = body['agents']
+ self.assertIsNotNone(agents)
+ # Get an agent.
+ agent = agents[0]
+ network_id = self.network['id']
+ resp, body = self.admin_client.remove_network_from_dhcp_agent(
+ agent_id=agent['id'],
+ network_id=network_id)
+ self.assertEqual(resp['status'], '204')
+ self.assertFalse(self._check_network_in_dhcp_agent(
+ network_id, agent))
+
+
+class DHCPAgentSchedulersTestXML(DHCPAgentSchedulersTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/network/base.py b/tempest/api/network/base.py
index b222ae3..61af91f 100644
--- a/tempest/api/network/base.py
+++ b/tempest/api/network/base.py
@@ -65,9 +65,13 @@
cls.members = []
cls.health_monitors = []
cls.vpnservices = []
+ cls.ikepolicies = []
@classmethod
def tearDownClass(cls):
+ # Clean up ike policies
+ for ikepolicy in cls.ikepolicies:
+ cls.client.delete_ike_policy(ikepolicy['id'])
# Clean up vpn services
for vpnservice in cls.vpnservices:
cls.client.delete_vpn_service(vpnservice['id'])
@@ -216,6 +220,14 @@
cls.vpnservices.append(vpnservice)
return vpnservice
+ @classmethod
+ def create_ike_policy(cls, name):
+ """Wrapper utility that returns a test ike policy."""
+ resp, body = cls.client.create_ike_policy(name)
+ ikepolicy = body['ikepolicy']
+ cls.ikepolicies.append(ikepolicy)
+ return ikepolicy
+
class BaseAdminNetworkTest(BaseNetworkTest):
diff --git a/tempest/api/network/test_security_groups_negative.py b/tempest/api/network/test_security_groups_negative.py
index cb0c247..32f4c95 100644
--- a/tempest/api/network/test_security_groups_negative.py
+++ b/tempest/api/network/test_security_groups_negative.py
@@ -15,10 +15,11 @@
# License for the specific language governing permissions and limitations
# under the License.
+import uuid
+
from tempest.api.network import base_security_groups as base
from tempest import exceptions
from tempest.test import attr
-import uuid
class NegativeSecGroupTest(base.BaseSecGroupTest):
@@ -74,6 +75,22 @@
port_range_max=pmax)
self.assertIn(msg, str(ex))
+ @attr(type=['negative', 'smoke'])
+ def test_create_additional_default_security_group_fails(self):
+ # Create security group named 'default', it should be failed.
+ name = 'default'
+ self.assertRaises(exceptions.Conflict,
+ self.client.create_security_group,
+ name)
+
+ @attr(type=['negative', 'smoke'])
+ def test_create_security_group_rule_with_non_existent_security_group(self):
+ # Create security group rules with not existing security group.
+ non_existent_sg = str(uuid.uuid4())
+ self.assertRaises(exceptions.NotFound,
+ self.client.create_security_group_rule,
+ non_existent_sg)
+
class NegativeSecGroupTestXML(NegativeSecGroupTest):
_interface = 'xml'
diff --git a/tempest/api/network/test_vpnaas_extensions.py b/tempest/api/network/test_vpnaas_extensions.py
index 9cbc7ac..7905b97 100644
--- a/tempest/api/network/test_vpnaas_extensions.py
+++ b/tempest/api/network/test_vpnaas_extensions.py
@@ -32,6 +32,7 @@
Create VPN Services
Update VPN Services
Delete VPN Services
+ List, Show, Create, Delete, and Update IKE policy
"""
@classmethod
@@ -43,6 +44,24 @@
cls.create_router_interface(cls.router['id'], cls.subnet['id'])
cls.vpnservice = cls.create_vpnservice(cls.subnet['id'],
cls.router['id'])
+ cls.ikepolicy = cls.create_ike_policy(data_utils.rand_name(
+ "ike-policy-"))
+
+ def _delete_ike_policy(self, ike_policy_id):
+ # Deletes a ike policy and verifies if it is deleted or not
+ ike_list = list()
+ resp, all_ike = self.client.list_ike_policies()
+ for ike in all_ike['ikepolicies']:
+ ike_list.append(ike['id'])
+ if ike_policy_id in ike_list:
+ resp, _ = self.client.delete_ike_policy(ike_policy_id)
+ self.assertEqual(204, resp.status)
+ # Asserting that the policy is not found in list after deletion
+ resp, ikepolicies = self.client.list_ike_policies()
+ ike_id_list = list()
+ for i in ikepolicies['ikepolicies']:
+ ike_id_list.append(i['id'])
+ self.assertNotIn(ike_policy_id, ike_id_list)
@attr(type='smoke')
def test_list_vpn_services(self):
@@ -94,3 +113,59 @@
self.assertEqual(self.vpnservice['router_id'], vpnservice['router_id'])
self.assertEqual(self.vpnservice['subnet_id'], vpnservice['subnet_id'])
self.assertEqual(self.vpnservice['tenant_id'], vpnservice['tenant_id'])
+
+ @attr(type='smoke')
+ def test_list_ike_policies(self):
+ # Verify the ike policy exists in the list of all IKE policies
+ resp, body = self.client.list_ike_policies()
+ self.assertEqual('200', resp['status'])
+ ikepolicies = body['ikepolicies']
+ self.assertIn(self.ikepolicy['id'], [i['id'] for i in ikepolicies])
+
+ @attr(type='smoke')
+ def test_create_update_delete_ike_policy(self):
+ # Creates a IKE policy
+ name = data_utils.rand_name('ike-policy-')
+ resp, body = (self.client.create_ike_policy(
+ name,
+ ike_version="v1",
+ encryption_algorithm="aes-128",
+ auth_algorithm="sha1"))
+ self.assertEqual('201', resp['status'])
+ ikepolicy = body['ikepolicy']
+ self.addCleanup(self._delete_ike_policy, ikepolicy['id'])
+ # Verification of ike policy update
+ description = "Updated ike policy"
+ new_ike = {'description': description, 'pfs': 'group5',
+ 'name': data_utils.rand_name("New-IKE-")}
+ resp, body = self.client.update_ike_policy(ikepolicy['id'],
+ **new_ike)
+ self.assertEqual('200', resp['status'])
+ updated_ike_policy = body['ikepolicy']
+ self.assertEqual(updated_ike_policy['description'], description)
+ # Verification of ike policy delete
+ resp, body = self.client.delete_ike_policy(ikepolicy['id'])
+ self.assertEqual('204', resp['status'])
+
+ @attr(type='smoke')
+ def test_show_ike_policy(self):
+ # Verifies the details of a ike policy
+ resp, body = self.client.show_ike_policy(self.ikepolicy['id'])
+ self.assertEqual('200', resp['status'])
+ ikepolicy = body['ikepolicy']
+ self.assertEqual(self.ikepolicy['id'], ikepolicy['id'])
+ self.assertEqual(self.ikepolicy['name'], ikepolicy['name'])
+ self.assertEqual(self.ikepolicy['description'],
+ ikepolicy['description'])
+ self.assertEqual(self.ikepolicy['encryption_algorithm'],
+ ikepolicy['encryption_algorithm'])
+ self.assertEqual(self.ikepolicy['auth_algorithm'],
+ ikepolicy['auth_algorithm'])
+ self.assertEqual(self.ikepolicy['tenant_id'],
+ ikepolicy['tenant_id'])
+ self.assertEqual(self.ikepolicy['pfs'],
+ ikepolicy['pfs'])
+ self.assertEqual(self.ikepolicy['phase1_negotiation_mode'],
+ ikepolicy['phase1_negotiation_mode'])
+ self.assertEqual(self.ikepolicy['ike_version'],
+ ikepolicy['ike_version'])
diff --git a/tempest/api/object_storage/test_container_staticweb.py b/tempest/api/object_storage/test_container_staticweb.py
index 94048f7..1eea30a 100644
--- a/tempest/api/object_storage/test_container_staticweb.py
+++ b/tempest/api/object_storage/test_container_staticweb.py
@@ -16,7 +16,7 @@
from tempest.api.object_storage import base
from tempest.common.utils import data_utils
-from tempest.test import attr, HTTP_SUCCESS
+from tempest import test
class StaticWebTest(base.BaseObjectTest):
@@ -48,7 +48,7 @@
cls.data.teardown_all()
super(StaticWebTest, cls).tearDownClass()
- @attr('gate')
+ @test.attr('gate')
def test_web_index(self):
headers = {'web-index': self.object_name}
@@ -59,7 +59,7 @@
# we should retrieve the self.object_name file
resp, body = self.custom_account_client.request("GET",
self.container_name)
- self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertIn(int(resp['status']), test.HTTP_SUCCESS)
self.assertEqual(body, self.object_data)
# clean up before exiting
@@ -70,7 +70,7 @@
self.container_name)
self.assertNotIn('x-container-meta-web-index', body)
- @attr('gate')
+ @test.attr('gate')
def test_web_listing(self):
headers = {'web-listings': 'true'}
@@ -81,7 +81,7 @@
# we should retrieve a listing of objects
resp, body = self.custom_account_client.request("GET",
self.container_name)
- self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertIn(int(resp['status']), test.HTTP_SUCCESS)
self.assertIn(self.object_name, body)
# clean up before exiting
@@ -92,7 +92,7 @@
self.container_name)
self.assertNotIn('x-container-meta-web-listings', body)
- @attr('gate')
+ @test.attr('gate')
def test_web_listing_css(self):
headers = {'web-listings': 'true',
'web-listings-css': 'listings.css'}
@@ -104,12 +104,12 @@
# we should retrieve a listing of objects
resp, body = self.custom_account_client.request("GET",
self.container_name)
- self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertIn(int(resp['status']), test.HTTP_SUCCESS)
self.assertIn(self.object_name, body)
css = '<link rel="stylesheet" type="text/css" href="listings.css" />'
self.assertIn(css, body)
- @attr('gate')
+ @test.attr('gate')
def test_web_error(self):
headers = {'web-listings': 'true',
'web-error': self.object_name}
diff --git a/tempest/api/object_storage/test_object_slo.py b/tempest/api/object_storage/test_object_slo.py
new file mode 100644
index 0000000..997120a
--- /dev/null
+++ b/tempest/api/object_storage/test_object_slo.py
@@ -0,0 +1,193 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 NTT Corporation
+#
+# 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 hashlib
+import json
+import re
+
+from tempest.api.object_storage import base
+from tempest.common import custom_matchers
+from tempest.common.utils import data_utils
+from tempest import exceptions
+from tempest import test
+
+# Each segment, except for the final one, must be at least 1 megabyte
+MIN_SEGMENT_SIZE = 1024 * 1024
+
+
+class ObjectSloTest(base.BaseObjectTest):
+
+ def setUp(self):
+ super(ObjectSloTest, self).setUp()
+ self.container_name = data_utils.rand_name(name='TestContainer')
+ self.container_client.create_container(self.container_name)
+ self.objects = []
+
+ def tearDown(self):
+ for obj in self.objects:
+ try:
+ self.object_client.delete_object(
+ self.container_name,
+ obj)
+ except exceptions.NotFound:
+ pass
+ self.container_client.delete_container(self.container_name)
+ super(ObjectSloTest, self).tearDown()
+
+ def _create_object(self, container_name, object_name, data, params=None):
+ resp, _ = self.object_client.create_object(container_name,
+ object_name,
+ data,
+ params)
+ self.objects.append(object_name)
+
+ return resp
+
+ def _create_manifest(self):
+ # Create a manifest file for SLO uploading
+ object_name = data_utils.rand_name(name='TestObject')
+ object_name_base_1 = object_name + '_01'
+ object_name_base_2 = object_name + '_02'
+ data_size = MIN_SEGMENT_SIZE
+ self.data = data_utils.arbitrary_string(data_size)
+ self._create_object(self.container_name,
+ object_name_base_1,
+ self.data)
+ self._create_object(self.container_name,
+ object_name_base_2,
+ self.data)
+
+ path_object_1 = '/%s/%s' % (self.container_name,
+ object_name_base_1)
+ path_object_2 = '/%s/%s' % (self.container_name,
+ object_name_base_2)
+ data_manifest = [{'path': path_object_1,
+ 'etag': hashlib.md5(self.data).hexdigest(),
+ 'size_bytes': data_size},
+ {'path': path_object_2,
+ 'etag': hashlib.md5(self.data).hexdigest(),
+ 'size_bytes': data_size}]
+
+ return json.dumps(data_manifest)
+
+ def _create_large_object(self):
+ # Create a large object for preparation of testing various SLO
+ # features
+ manifest = self._create_manifest()
+
+ params = {'multipart-manifest': 'put'}
+ object_name = data_utils.rand_name(name='TestObject')
+ self._create_object(self.container_name,
+ object_name,
+ manifest,
+ params)
+ return object_name
+
+ def _assertHeadersSLO(self, resp, method):
+ # Check the existence of common headers with custom matcher
+ self.assertThat(resp, custom_matchers.ExistsAllResponseHeaders(
+ 'Object', method))
+ # When sending GET or HEAD requests to SLO the response contains
+ # 'X-Static-Large-Object' header
+ if method in ('GET', 'HEAD'):
+ self.assertIn('x-static-large-object', resp)
+
+ # Check common headers for all HTTP methods
+ self.assertTrue(re.match("^tx[0-9a-f]*-[0-9a-f]*$",
+ resp['x-trans-id']))
+ self.assertTrue(resp['content-length'].isdigit())
+ self.assertNotEqual(len(resp['date']), 0)
+ # Etag value of a large object is enclosed in double-quotations.
+ self.assertTrue(resp['etag'].startswith('\"'))
+ self.assertTrue(resp['etag'].endswith('\"'))
+ self.assertTrue(resp['etag'].strip('\"').isalnum())
+ # Check header formats for a specific method
+ if method in ('GET', 'HEAD'):
+ self.assertTrue(re.match("^\d+\.?\d*\Z", resp['x-timestamp']))
+ self.assertNotEqual(len(resp['content-type']), 0)
+ self.assertEqual(resp['accept-ranges'], 'bytes')
+ self.assertEqual(resp['x-static-large-object'], 'True')
+
+ @test.attr(type='gate')
+ def test_upload_manifest(self):
+ # create static large object from multipart manifest
+ manifest = self._create_manifest()
+
+ params = {'multipart-manifest': 'put'}
+ object_name = data_utils.rand_name(name='TestObject')
+ resp = self._create_object(self.container_name,
+ object_name,
+ manifest,
+ params)
+
+ self.assertIn(int(resp['status']), test.HTTP_SUCCESS)
+ self._assertHeadersSLO(resp, 'PUT')
+
+ @test.attr(type='gate')
+ def test_list_large_object_metadata(self):
+ # list static large object metadata using multipart manifest
+ object_name = self._create_large_object()
+
+ resp, body = self.object_client.list_object_metadata(
+ self.container_name,
+ object_name)
+
+ self.assertIn(int(resp['status']), test.HTTP_SUCCESS)
+ self._assertHeadersSLO(resp, 'HEAD')
+
+ @test.attr(type='gate')
+ def test_retrieve_large_object(self):
+ # list static large object using multipart manifest
+ object_name = self._create_large_object()
+
+ resp, body = self.object_client.get_object(
+ self.container_name,
+ object_name)
+
+ self.assertIn(int(resp['status']), test.HTTP_SUCCESS)
+ self._assertHeadersSLO(resp, 'GET')
+
+ sum_data = self.data + self.data
+ self.assertEqual(body, sum_data)
+
+ @test.attr(type='gate')
+ def test_delete_large_object(self):
+ # delete static large object using multipart manifest
+ object_name = self._create_large_object()
+
+ params_del = {'multipart-manifest': 'delete'}
+ resp, body = self.object_client.delete_object(
+ self.container_name,
+ object_name,
+ params=params_del)
+
+ self.assertIn(int(resp['status']), test.HTTP_SUCCESS)
+
+ # When deleting SLO using multipart manifest, the response contains
+ # not 'content-length' but 'transfer-encoding' header. This is the
+ # special case, therefore the existence of response headers is checked
+ # outside of custom matcher.
+ self.assertIn('transfer-encoding', resp)
+ self.assertIn('content-type', resp)
+ self.assertIn('x-trans-id', resp)
+ self.assertIn('date', resp)
+
+ # Check only the format of common headers with custom matcher
+ self.assertThat(resp, custom_matchers.AreAllWellFormatted())
+
+ resp, body = self.container_client.list_container_contents(
+ self.container_name)
+ self.assertEqual(int(resp['x-container-object-count']), 0)
diff --git a/tempest/api/volume/admin/test_volume_hosts.py b/tempest/api/volume/admin/test_volume_hosts.py
new file mode 100644
index 0000000..e7d8c02
--- /dev/null
+++ b/tempest/api/volume/admin/test_volume_hosts.py
@@ -0,0 +1,34 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# 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.api.volume import base
+from tempest.test import attr
+
+
+class VolumeHostsAdminTestsJSON(base.BaseVolumeAdminTest):
+ _interface = "json"
+
+ @attr(type='gate')
+ def test_list_hosts(self):
+ resp, hosts = self.hosts_client.list_hosts()
+ self.assertEqual(200, resp.status)
+ self.assertTrue(len(hosts) >= 2, "No. of hosts are < 2,"
+ "response of list hosts is: % s" % hosts)
+
+
+class VolumeHostsAdminTestsXML(VolumeHostsAdminTestsJSON):
+ _interface = 'xml'
diff --git a/tempest/api/volume/admin/test_volumes_actions.py b/tempest/api/volume/admin/test_volumes_actions.py
index 4063eef..cb9ff11 100644
--- a/tempest/api/volume/admin/test_volumes_actions.py
+++ b/tempest/api/volume/admin/test_volumes_actions.py
@@ -47,7 +47,7 @@
super(VolumesActionsTest, cls).tearDownClass()
def _reset_volume_status(self, volume_id, status):
- #Reset the volume status
+ # Reset the volume status
resp, body = self.admin_volume_client.reset_volume_status(volume_id,
status)
return resp, body
@@ -57,6 +57,26 @@
self._reset_volume_status(self.volume['id'], 'available')
super(VolumesActionsTest, self).tearDown()
+ def _create_temp_volume(self):
+ # Create a temp volume for force delete tests
+ vol_name = utils.rand_name('Volume')
+ resp, temp_volume = self.client.create_volume(size=1,
+ display_name=vol_name)
+ self.client.wait_for_volume_status(temp_volume['id'], 'available')
+
+ return temp_volume
+
+ def _create_reset_and_force_delete_temp_volume(self, status=None):
+ # Create volume, reset volume status, and force delete temp volume
+ temp_volume = self._create_temp_volume()
+ if status:
+ resp, body = self._reset_volume_status(temp_volume['id'], status)
+ self.assertEqual(202, resp.status)
+ resp_delete, volume_delete = self.admin_volume_client.\
+ force_delete_volume(temp_volume['id'])
+ self.assertEqual(202, resp_delete.status)
+ self.client.wait_for_resource_deletion(temp_volume['id'])
+
@attr(type='gate')
def test_volume_reset_status(self):
# test volume reset status : available->error->available
@@ -84,6 +104,19 @@
resp_get, volume_get = self.client.get_volume(self.volume['id'])
self.assertEqual('in-use', volume_get['status'])
+ def test_volume_force_delete_when_volume_is_creating(self):
+ # test force delete when status of volume is creating
+ self._create_reset_and_force_delete_temp_volume('creating')
+
+ def test_volume_force_delete_when_volume_is_attaching(self):
+ # test force delete when status of volume is attaching
+ self._create_reset_and_force_delete_temp_volume('attaching')
+
+ @attr(type='gate')
+ def test_volume_force_delete_when_volume_is_error(self):
+ # test force delete when status of volume is error
+ self._create_reset_and_force_delete_temp_volume('error')
+
class VolumesActionsTestXML(VolumesActionsTest):
_interface = "xml"
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index cdf8638..465f570 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -151,3 +151,4 @@
else:
cls.os_adm = clients.AdminManager(interface=cls._interface)
cls.client = cls.os_adm.volume_types_client
+ cls.hosts_client = cls.os_adm.volume_hosts_client
diff --git a/tempest/clients.py b/tempest/clients.py
index 291b946..ac79ce0 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -62,6 +62,8 @@
ServersV3ClientJSON
from tempest.services.compute.v3.json.services_client import \
ServicesV3ClientJSON
+from tempest.services.compute.v3.json.tenant_usages_client import \
+ TenantUsagesV3ClientJSON
from tempest.services.compute.v3.xml.availability_zone_client import \
AvailabilityZoneV3ClientXML
from tempest.services.compute.v3.xml.extensions_client import \
@@ -73,6 +75,8 @@
from tempest.services.compute.v3.xml.servers_client import ServersV3ClientXML
from tempest.services.compute.v3.xml.services_client import \
ServicesV3ClientXML
+from tempest.services.compute.v3.xml.tenant_usages_client import \
+ TenantUsagesV3ClientXML
from tempest.services.compute.xml.aggregates_client import AggregatesClientXML
from tempest.services.compute.xml.availability_zone_client import \
AvailabilityZoneClientXML
@@ -136,10 +140,14 @@
ObjectClientCustomizedHeader
from tempest.services.orchestration.json.orchestration_client import \
OrchestrationClient
+from tempest.services.volume.json.admin.volume_hosts_client import \
+ VolumeHostsClientJSON
from tempest.services.volume.json.admin.volume_types_client import \
VolumeTypesClientJSON
from tempest.services.volume.json.snapshots_client import SnapshotsClientJSON
from tempest.services.volume.json.volumes_client import VolumesClientJSON
+from tempest.services.volume.xml.admin.volume_hosts_client import \
+ VolumeHostsClientXML
from tempest.services.volume.xml.admin.volume_types_client import \
VolumeTypesClientXML
from tempest.services.volume.xml.snapshots_client import SnapshotsClientXML
@@ -230,6 +238,8 @@
self.service_client = ServiceClientXML(*client_args)
self.aggregates_client = AggregatesClientXML(*client_args)
self.services_client = ServicesClientXML(*client_args)
+ self.tenant_usages_v3_client = TenantUsagesV3ClientXML(
+ *client_args)
self.tenant_usages_client = TenantUsagesClientXML(*client_args)
self.policy_client = PolicyClientXML(*client_args)
self.hypervisor_v3_client = HypervisorV3ClientXML(*client_args)
@@ -239,6 +249,7 @@
self.credentials_client = CredentialsClientXML(*client_args)
self.instance_usages_audit_log_client = \
InstanceUsagesAuditLogClientXML(*client_args)
+ self.volume_hosts_client = VolumeHostsClientXML(*client_args)
if client_args_v3_auth:
self.servers_client_v3_auth = ServersClientXML(
@@ -278,6 +289,8 @@
self.service_client = ServiceClientJSON(*client_args)
self.aggregates_client = AggregatesClientJSON(*client_args)
self.services_client = ServicesClientJSON(*client_args)
+ self.tenant_usages_v3_client = TenantUsagesV3ClientJSON(
+ *client_args)
self.tenant_usages_client = TenantUsagesClientJSON(*client_args)
self.policy_client = PolicyClientJSON(*client_args)
self.hypervisor_v3_client = HypervisorV3ClientJSON(*client_args)
@@ -287,6 +300,7 @@
self.credentials_client = CredentialsClientJSON(*client_args)
self.instance_usages_audit_log_client = \
InstanceUsagesAuditLogClientJSON(*client_args)
+ self.volume_hosts_client = VolumeHostsClientJSON(*client_args)
if client_args_v3_auth:
self.servers_client_v3_auth = ServersClientJSON(
diff --git a/tempest/common/ssh.py b/tempest/common/ssh.py
index 742a354..c397b7c 100644
--- a/tempest/common/ssh.py
+++ b/tempest/common/ssh.py
@@ -62,7 +62,7 @@
password=self.password,
look_for_keys=self.look_for_keys,
key_filename=self.key_filename,
- timeout=self.timeout, pkey=self.pkey)
+ timeout=self.channel_timeout, pkey=self.pkey)
_timeout = False
break
except (socket.error,
diff --git a/tempest/config.py b/tempest/config.py
index d97c7b2..220fd04 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -103,14 +103,6 @@
"users. This option enables isolated test cases and "
"better parallel execution, but also requires that "
"OpenStack Identity API admin credentials are known."),
- cfg.BoolOpt('allow_tenant_reuse',
- default=True,
- help="If allow_tenant_isolation is True and a tenant that "
- "would be created for a given test already exists (such "
- "as from a previously-failed run), re-use that tenant "
- "instead of failing because of the conflict. Note that "
- "this would result in the tenant being deleted at the "
- "end of a subsequent successful run."),
cfg.StrOpt('image_ref',
default="{$IMAGE_ID}",
help="Valid secondary image reference to be used in tests."),
diff --git a/tempest/services/compute/v3/json/tenant_usages_client.py b/tempest/services/compute/v3/json/tenant_usages_client.py
index 4dd6964..298f363 100644
--- a/tempest/services/compute/v3/json/tenant_usages_client.py
+++ b/tempest/services/compute/v3/json/tenant_usages_client.py
@@ -21,12 +21,12 @@
from tempest.common.rest_client import RestClient
-class TenantUsagesClientJSON(RestClient):
+class TenantUsagesV3ClientJSON(RestClient):
def __init__(self, config, username, password, auth_url, tenant_name=None):
- super(TenantUsagesClientJSON, self).__init__(
+ super(TenantUsagesV3ClientJSON, self).__init__(
config, username, password, auth_url, tenant_name)
- self.service = self.config.compute.catalog_type
+ self.service = self.config.compute.catalog_v3_type
def list_tenant_usages(self, params=None):
url = 'os-simple-tenant-usage'
diff --git a/tempest/services/compute/v3/xml/tenant_usages_client.py b/tempest/services/compute/v3/xml/tenant_usages_client.py
index cb92324..790bd5c 100644
--- a/tempest/services/compute/v3/xml/tenant_usages_client.py
+++ b/tempest/services/compute/v3/xml/tenant_usages_client.py
@@ -23,13 +23,13 @@
from tempest.services.compute.xml.common import xml_to_json
-class TenantUsagesClientXML(RestClientXML):
+class TenantUsagesV3ClientXML(RestClientXML):
def __init__(self, config, username, password, auth_url, tenant_name=None):
- super(TenantUsagesClientXML, self).__init__(config, username,
- password, auth_url,
- tenant_name)
- self.service = self.config.compute.catalog_type
+ super(TenantUsagesV3ClientXML, self).__init__(config, username,
+ password, auth_url,
+ tenant_name)
+ self.service = self.config.compute.catalog_v3_type
def _parse_array(self, node):
json = xml_to_json(node)
diff --git a/tempest/services/network/json/network_client.py b/tempest/services/network/json/network_client.py
index aa7f2f2..c6bd423 100644
--- a/tempest/services/network/json/network_client.py
+++ b/tempest/services/network/json/network_client.py
@@ -697,3 +697,60 @@
resp, body = self.get(uri, self.headers)
body = json.loads(body)
return resp, body
+
+ def list_dhcp_agent_hosting_network(self, network_id):
+ uri = '%s/networks/%s/dhcp-agents' % (self.uri_prefix, network_id)
+ resp, body = self.get(uri, self.headers)
+ body = json.loads(body)
+ return resp, body
+
+ def list_networks_hosted_by_one_dhcp_agent(self, agent_id):
+ uri = '%s/agents/%s/dhcp-networks' % (self.uri_prefix, agent_id)
+ resp, body = self.get(uri, self.headers)
+ body = json.loads(body)
+ return resp, body
+
+ def remove_network_from_dhcp_agent(self, agent_id, network_id):
+ uri = '%s/agents/%s/dhcp-networks/%s' % (self.uri_prefix, agent_id,
+ network_id)
+ resp, body = self.delete(uri, self.headers)
+ return resp, body
+
+ def list_ike_policies(self):
+ uri = '%s/vpn/ikepolicies' % (self.uri_prefix)
+ resp, body = self.get(uri, self.headers)
+ body = json.loads(body)
+ return resp, body
+
+ def create_ike_policy(self, name, **kwargs):
+ post_body = {
+ "ikepolicy": {
+ "name": name,
+ }
+ }
+ for key, val in kwargs.items():
+ post_body['ikepolicy'][key] = val
+ body = json.dumps(post_body)
+ uri = '%s/vpn/ikepolicies' % (self.uri_prefix)
+ resp, body = self.post(uri, headers=self.headers, body=body)
+ body = json.loads(body)
+ return resp, body
+
+ def show_ike_policy(self, uuid):
+ uri = '%s/vpn/ikepolicies/%s' % (self.uri_prefix, uuid)
+ resp, body = self.get(uri, self.headers)
+ body = json.loads(body)
+ return resp, body
+
+ def delete_ike_policy(self, uuid):
+ uri = '%s/vpn/ikepolicies/%s' % (self.uri_prefix, uuid)
+ resp, body = self.delete(uri, self.headers)
+ return resp, body
+
+ def update_ike_policy(self, uuid, **kwargs):
+ put_body = {'ikepolicy': kwargs}
+ body = json.dumps(put_body)
+ uri = '%s/vpn/ikepolicies/%s' % (self.uri_prefix, uuid)
+ resp, body = self.put(uri, body=body, headers=self.headers)
+ body = json.loads(body)
+ return resp, body
diff --git a/tempest/services/network/xml/network_client.py b/tempest/services/network/xml/network_client.py
index 5af2dfb..e11d4c1 100755
--- a/tempest/services/network/xml/network_client.py
+++ b/tempest/services/network/xml/network_client.py
@@ -580,6 +580,26 @@
body = {'service_providers': providers}
return resp, body
+ def list_dhcp_agent_hosting_network(self, network_id):
+ uri = '%s/networks/%s/dhcp-agents' % (self.uri_prefix, network_id)
+ resp, body = self.get(uri, self.headers)
+ agents = self._parse_array(etree.fromstring(body))
+ body = {'agents': agents}
+ return resp, body
+
+ def list_networks_hosted_by_one_dhcp_agent(self, agent_id):
+ uri = '%s/agents/%s/dhcp-networks' % (self.uri_prefix, agent_id)
+ resp, body = self.get(uri, self.headers)
+ networks = self._parse_array(etree.fromstring(body))
+ body = {'networks': networks}
+ return resp, body
+
+ def remove_network_from_dhcp_agent(self, agent_id, network_id):
+ uri = '%s/agents/%s/dhcp-networks/%s' % (self.uri_prefix, agent_id,
+ network_id)
+ resp, body = self.delete(uri, self.headers)
+ return resp, body
+
def _root_tag_fetcher_and_xml_to_json_parse(xml_returned_body):
body = ET.fromstring(xml_returned_body)
diff --git a/tempest/services/object_storage/object_client.py b/tempest/services/object_storage/object_client.py
index 2fee042..9e0adff 100644
--- a/tempest/services/object_storage/object_client.py
+++ b/tempest/services/object_storage/object_client.py
@@ -15,6 +15,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import urllib
+
from tempest.common import http
from tempest.common.rest_client import RestClient
from tempest import exceptions
@@ -27,13 +29,16 @@
self.service = self.config.object_storage.catalog_type
- def create_object(self, container, object_name, data):
+ def create_object(self, container, object_name, data, params=None):
"""Create storage object."""
headers = dict(self.headers)
if not data:
headers['content-length'] = '0'
url = "%s/%s" % (str(container), str(object_name))
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
resp, body = self.put(url, data, headers)
return resp, body
@@ -41,9 +46,11 @@
"""Upload data to replace current storage object."""
return self.create_object(container, object_name, data)
- def delete_object(self, container, object_name):
+ def delete_object(self, container, object_name, params=None):
"""Delete storage object."""
url = "%s/%s" % (str(container), str(object_name))
+ if params:
+ url += '?%s' % urllib.urlencode(params)
resp, body = self.delete(url)
return resp, body
diff --git a/tempest/services/volume/json/admin/volume_hosts_client.py b/tempest/services/volume/json/admin/volume_hosts_client.py
new file mode 100644
index 0000000..fc28ada
--- /dev/null
+++ b/tempest/services/volume/json/admin/volume_hosts_client.py
@@ -0,0 +1,46 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# 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.
+
+import json
+import urllib
+
+from tempest.common.rest_client import RestClient
+
+
+class VolumeHostsClientJSON(RestClient):
+ """
+ Client class to send CRUD Volume Hosts API requests to a Cinder endpoint
+ """
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(VolumeHostsClientJSON, self).__init__(config, username, password,
+ auth_url, tenant_name)
+
+ self.service = self.config.volume.catalog_type
+ self.build_interval = self.config.volume.build_interval
+ self.build_timeout = self.config.volume.build_timeout
+
+ def list_hosts(self, params=None):
+ """Lists all hosts."""
+
+ url = 'os-hosts'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ return resp, body['hosts']
diff --git a/tempest/services/volume/json/volumes_client.py b/tempest/services/volume/json/volumes_client.py
index b4a1a68..967dc09 100644
--- a/tempest/services/volume/json/volumes_client.py
+++ b/tempest/services/volume/json/volumes_client.py
@@ -256,3 +256,10 @@
url = 'volumes/%s/action' % (volume_id)
resp, body = self.post(url, post_body, self.headers)
return resp, body
+
+ def force_delete_volume(self, volume_id):
+ """Force Delete Volume."""
+ post_body = json.dumps({'os-force_delete': {}})
+ resp, body = self.post('volumes/%s/action' % volume_id, post_body,
+ self.headers)
+ return resp, body
diff --git a/tempest/services/volume/xml/admin/volume_hosts_client.py b/tempest/services/volume/xml/admin/volume_hosts_client.py
new file mode 100644
index 0000000..59ce933
--- /dev/null
+++ b/tempest/services/volume/xml/admin/volume_hosts_client.py
@@ -0,0 +1,72 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# 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.
+
+import urllib
+
+from lxml import etree
+
+from tempest.common.rest_client import RestClientXML
+from tempest.services.compute.xml.common import xml_to_json
+
+
+class VolumeHostsClientXML(RestClientXML):
+ """
+ Client class to send CRUD Volume Hosts API requests to a Cinder endpoint
+ """
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(VolumeHostsClientXML, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.volume.catalog_type
+ self.build_interval = self.config.compute.build_interval
+ self.build_timeout = self.config.compute.build_timeout
+
+ def _parse_array(self, node):
+ """
+ This method is to parse the "list" response body
+ Eg:
+
+ <?xml version='1.0' encoding='UTF-8'?>
+ <hosts>
+ <host service-status="available" service="cinder-scheduler"/>
+ <host service-status="available" service="cinder-volume"/>
+ </hosts>
+
+ This method will append the details of specified tag,
+ here it is "host"
+ Return value would be list of hosts as below
+
+ [{'service-status': 'available', 'service': 'cinder-scheduler'},
+ {'service-status': 'available', 'service': 'cinder-volume'}]
+ """
+ array = []
+ for child in node.getchildren():
+ tag_list = child.tag.split('}', 1)
+ if tag_list[0] == "host":
+ array.append(xml_to_json(child))
+ return array
+
+ def list_hosts(self, params=None):
+ """List all the hosts."""
+ url = 'os-hosts'
+
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url, self.headers)
+ body = self._parse_array(etree.fromstring(body))
+ return resp, body
diff --git a/tempest/services/volume/xml/volumes_client.py b/tempest/services/volume/xml/volumes_client.py
index 21254aa..1fc63e9 100644
--- a/tempest/services/volume/xml/volumes_client.py
+++ b/tempest/services/volume/xml/volumes_client.py
@@ -347,3 +347,12 @@
if body:
body = xml_to_json(etree.fromstring(body))
return resp, body
+
+ def force_delete_volume(self, volume_id):
+ """Force Delete Volume."""
+ post_body = Element("os-force_delete")
+ url = 'volumes/%s/action' % str(volume_id)
+ resp, body = self.post(url, str(Document(post_body)), self.headers)
+ if body:
+ body = xml_to_json(etree.fromstring(body))
+ return resp, body
diff --git a/tempest/stress/run_stress.py b/tempest/stress/run_stress.py
index e5cc281..067b994 100755
--- a/tempest/stress/run_stress.py
+++ b/tempest/stress/run_stress.py
@@ -24,6 +24,7 @@
from unittest import loader
from tempest.openstack.common import log as logging
+from tempest.stress import driver
LOG = logging.getLogger(__name__)
@@ -68,8 +69,6 @@
def main(ns):
- # NOTE(mkoderer): moved import to make "-h" possible without OpenStack
- from tempest.stress import driver
result = 0
if not ns.all:
tests = json.load(open(ns.tests, 'r'))
diff --git a/tempest/tests/stress/__init__.py b/tempest/tests/stress/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/tests/stress/__init__.py
diff --git a/tempest/tests/stress/test_stress.py b/tempest/tests/stress/test_stress.py
new file mode 100644
index 0000000..4d7de9d
--- /dev/null
+++ b/tempest/tests/stress/test_stress.py
@@ -0,0 +1,57 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 Deutsche Telekom AG
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import shlex
+import subprocess
+
+import tempest.cli as cli
+from tempest.openstack.common import log as logging
+import tempest.test
+
+LOG = logging.getLogger(__name__)
+
+
+class StressFrameworkTest(tempest.test.BaseTestCase):
+ """Basic test for the stress test framework.
+ """
+
+ def _cmd(self, cmd, param):
+ """Executes specified command."""
+ cmd = ' '.join([cmd, param])
+ LOG.info("running: '%s'" % cmd)
+ cmd_str = cmd
+ cmd = shlex.split(cmd)
+ result = ''
+ result_err = ''
+ try:
+ stdout = subprocess.PIPE
+ stderr = subprocess.PIPE
+ proc = subprocess.Popen(
+ cmd, stdout=stdout, stderr=stderr)
+ result, result_err = proc.communicate()
+ if proc.returncode != 0:
+ LOG.debug('error of %s:\n%s' % (cmd_str, result_err))
+ raise cli.CommandFailed(proc.returncode,
+ cmd,
+ result)
+ finally:
+ LOG.debug('output of %s:\n%s' % (cmd_str, result))
+ return proc.returncode
+
+ def test_help_function(self):
+ result = self._cmd("python", "-m tempest.stress.run_stress -h")
+ self.assertEqual(0, result)
diff --git a/tempest/tests/stress/test_stressaction.py b/tempest/tests/stress/test_stressaction.py
new file mode 100644
index 0000000..3d2901e
--- /dev/null
+++ b/tempest/tests/stress/test_stressaction.py
@@ -0,0 +1,65 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 Deutsche Telekom AG
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import tempest.stress.stressaction as stressaction
+import tempest.test
+
+
+class FakeStressAction(stressaction.StressAction):
+ def __init__(self, manager, max_runs=None, stop_on_error=False):
+ super(self.__class__, self).__init__(manager, max_runs, stop_on_error)
+ self._run_called = False
+
+ def run(self):
+ self._run_called = True
+
+ @property
+ def run_called(self):
+ return self._run_called
+
+
+class FakeStressActionFailing(stressaction.StressAction):
+ def run(self):
+ raise Exception('FakeStressActionFailing raise exception')
+
+
+class TestStressAction(tempest.test.BaseTestCase):
+ def _bulid_stats_dict(self, runs=0, fails=0):
+ return {'runs': runs, 'fails': fails}
+
+ def testStressTestRun(self):
+ stressAction = FakeStressAction(manager=None, max_runs=1)
+ stats = self._bulid_stats_dict()
+ stressAction.execute(stats)
+ self.assertTrue(stressAction.run_called)
+ self.assertEqual(stats['runs'], 1)
+ self.assertEqual(stats['fails'], 0)
+
+ def testStressMaxTestRuns(self):
+ stressAction = FakeStressAction(manager=None, max_runs=500)
+ stats = self._bulid_stats_dict(runs=499)
+ stressAction.execute(stats)
+ self.assertTrue(stressAction.run_called)
+ self.assertEqual(stats['runs'], 500)
+ self.assertEqual(stats['fails'], 0)
+
+ def testStressTestRunWithException(self):
+ stressAction = FakeStressActionFailing(manager=None, max_runs=1)
+ stats = self._bulid_stats_dict()
+ stressAction.execute(stats)
+ self.assertEqual(stats['runs'], 1)
+ self.assertEqual(stats['fails'], 1)
diff --git a/test-requirements.txt b/test-requirements.txt
index fbe7e43..41a784e 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -1,4 +1,4 @@
-hacking>=0.5.6,<0.8
+hacking>=0.8.0,<0.9
# needed for doc build
docutils==0.9.1
sphinx>=1.1.2
diff --git a/tools/verify_tempest_config.py b/tools/verify_tempest_config.py
index 1b5fe68..347659d 100755
--- a/tools/verify_tempest_config.py
+++ b/tools/verify_tempest_config.py
@@ -36,11 +36,11 @@
__, versions = os.image_client.get_versions()
if CONF.image_feature_enabled.api_v1 != ('v1.1' in versions or 'v1.0' in
versions):
- print 'Config option image api_v1 should be change to: %s' % (
- not CONF.image_feature_enabled.api_v1)
+ print('Config option image api_v1 should be change to: %s' % (
+ not CONF.image_feature_enabled.api_v1))
if CONF.image_feature_enabled.api_v2 != ('v2.0' in versions):
- print 'Config option image api_v2 should be change to: %s' % (
- not CONF.image_feature_enabled.api_v2)
+ print('Config option image api_v2 should be change to: %s' % (
+ not CONF.image_feature_enabled.api_v2))
def verify_extensions(os):
@@ -62,8 +62,8 @@
for option in NOVA_EXTENSIONS.keys():
config_value = getattr(CONF.compute_feature_enabled, option)
if config_value != results['nova_features'][option]:
- print "Config option: %s should be changed to: %s" % (
- option, not config_value)
+ print("Config option: %s should be changed to: %s" % (
+ option, not config_value))
def main(argv):