Merge "port test_simple_tenant_usage into nova v3 part2"
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/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/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/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/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/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/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 27191f6..ac79ce0 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -140,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
@@ -245,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(
@@ -295,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/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/network/json/network_client.py b/tempest/services/network/json/network_client.py
index aa7f2f2..aab2b9b 100644
--- a/tempest/services/network/json/network_client.py
+++ b/tempest/services/network/json/network_client.py
@@ -697,3 +697,21 @@
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
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/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/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/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)