Merge "Skip test tokens"
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 2ecace0..78e6aac 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -430,3 +430,7 @@
log_check_interval = 60
# The default number of threads created while stress test
default_thread_number_per_action=4
+
+[debug]
+# Enable diagnostic commands
+enable = True
diff --git a/tempest/api/compute/admin/test_hosts.py b/tempest/api/compute/admin/test_hosts.py
index af76ad0..19d7973 100644
--- a/tempest/api/compute/admin/test_hosts.py
+++ b/tempest/api/compute/admin/test_hosts.py
@@ -15,6 +15,7 @@
# under the License.
from tempest.api.compute import base
+from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
from tempest.test import attr
@@ -71,6 +72,30 @@
self.assertRaises(exceptions.Unauthorized,
self.non_admin_client.list_hosts)
+ @attr(type='gate')
+ def test_show_host_detail(self):
+ resp, hosts = self.client.list_hosts()
+ self.assertEqual(200, resp.status)
+ self.assertTrue(len(hosts) >= 1)
+ hostname = hosts[0]['host_name']
+
+ resp, resources = self.client.show_host_detail(hostname)
+ self.assertEqual(200, resp.status)
+ self.assertTrue(len(resources) >= 1)
+ host_resource = resources[0]['resource']
+ self.assertIsNotNone(host_resource)
+ self.assertIsNotNone(host_resource['cpu'])
+ self.assertIsNotNone(host_resource['disk_gb'])
+ self.assertIsNotNone(host_resource['memory_mb'])
+ self.assertIsNotNone(host_resource['project'])
+ self.assertEqual(hostname, host_resource['host'])
+
+ @attr(type='negative')
+ def test_show_host_detail_with_nonexist_hostname(self):
+ hostname = rand_name('rand_hostname')
+ self.assertRaises(exceptions.NotFound,
+ self.client.show_host_detail, hostname)
+
class HostsAdminTestXML(HostsAdminTestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/images/test_images.py b/tempest/api/compute/images/test_images.py
index 2f0ed6b..97fbd8b 100644
--- a/tempest/api/compute/images/test_images.py
+++ b/tempest/api/compute/images/test_images.py
@@ -166,7 +166,7 @@
def test_delete_image_id_is_over_35_character_limit(self):
# Return an error while trying to delete image with id over limit
self.assertRaises(exceptions.NotFound, self.client.delete_image,
- '11a22b9-120q-5555-cc11-00ab112223gj-3fac')
+ '11a22b9-12a9-5555-cc11-00ab112223fa-3fac')
class ImagesTestXML(ImagesTestJSON):
diff --git a/tempest/api/identity/admin/test_users.py b/tempest/api/identity/admin/test_users.py
index 057e633..dc17e90 100644
--- a/tempest/api/identity/admin/test_users.py
+++ b/tempest/api/identity/admin/test_users.py
@@ -102,11 +102,39 @@
self.client.clear_auth()
@attr(type='smoke')
+ def test_update_user(self):
+ # Test case to check if updating of user attributes is successful.
+ test_user = rand_name('test_user_')
+ self.data.setup_test_tenant()
+ resp, user = self.client.create_user(test_user, self.alt_password,
+ self.data.tenant['id'],
+ self.alt_email)
+ # Delete the User at the end of this method
+ self.addCleanup(self.client.delete_user, user['id'])
+ # Updating user details with new values
+ u_name2 = rand_name('user2-')
+ u_email2 = u_name2 + '@testmail.tm'
+ resp, update_user = self.client.update_user(user['id'], name=u_name2,
+ email=u_email2,
+ enabled=False)
+ # Assert response body of update user.
+ self.assertEqual(200, resp.status)
+ self.assertEqual(u_name2, update_user['name'])
+ self.assertEqual(u_email2, update_user['email'])
+ self.assertEqual('false', str(update_user['enabled']).lower())
+ # GET by id after updating
+ resp, updated_user = self.client.get_user(user['id'])
+ # Assert response body of GET after updating
+ self.assertEqual(u_name2, updated_user['name'])
+ self.assertEqual(u_email2, updated_user['email'])
+ self.assertEqual('false', str(updated_user['enabled']).lower())
+
+ @attr(type='smoke')
def test_delete_user(self):
# Delete a user
- alt_user2 = rand_name('alt_user_')
+ test_user = rand_name('test_user_')
self.data.setup_test_tenant()
- resp, user = self.client.create_user(alt_user2, self.alt_password,
+ resp, user = self.client.create_user(test_user, self.alt_password,
self.data.tenant['id'],
self.alt_email)
self.assertEqual('200', resp['status'])
diff --git a/tempest/api/volume/test_volumes_get.py b/tempest/api/volume/test_volumes_get.py
index 12b03b5..70fe1a3 100644
--- a/tempest/api/volume/test_volumes_get.py
+++ b/tempest/api/volume/test_volumes_get.py
@@ -34,7 +34,7 @@
self.assertEqual(202, resp.status)
self.client.wait_for_resource_deletion(volume_id)
- def _volume_create_get_delete(self, **kwargs):
+ def _volume_create_get_update_delete(self, **kwargs):
# Create a volume, Get it's details and Delete the volume
volume = {}
v_name = rand_name('Volume')
@@ -74,6 +74,29 @@
if 'imageRef' not in kwargs:
self.assertEqual(fetched_volume['bootable'], False)
+ # Update Volume
+ new_v_name = rand_name('new-Volume')
+ new_desc = 'This is the new description of volume'
+ resp, update_volume = \
+ self.client.update_volume(volume['id'],
+ display_name=new_v_name,
+ display_description=new_desc)
+ # Assert response body for update_volume method
+ self.assertEqual(200, resp.status)
+ self.assertEqual(new_v_name, update_volume['display_name'])
+ self.assertEqual(new_desc, update_volume['display_description'])
+ # Assert response body for get_volume method
+ resp, updated_volume = self.client.get_volume(volume['id'])
+ self.assertEqual(200, resp.status)
+ self.assertEqual(volume['id'], updated_volume['id'])
+ self.assertEqual(new_v_name, updated_volume['display_name'])
+ self.assertEqual(new_desc, updated_volume['display_description'])
+ self.assertEqual(metadata, updated_volume['metadata'])
+ if 'imageRef' in kwargs:
+ self.assertEqual(updated_volume['bootable'], True)
+ if 'imageRef' not in kwargs:
+ self.assertEqual(updated_volume['bootable'], False)
+
@attr(type='gate')
def test_volume_get_metadata_none(self):
# Create a volume without passing metadata, get details, and delete
@@ -94,19 +117,20 @@
self.assertEqual(fetched_volume['metadata'], {})
@attr(type='smoke')
- def test_volume_create_get_delete(self):
- self._volume_create_get_delete()
+ def test_volume_create_get_update_delete(self):
+ self._volume_create_get_update_delete()
@attr(type='smoke')
@services('image')
- def test_volume_create_get_delete_from_image(self):
- self._volume_create_get_delete(imageRef=self.config.compute.image_ref)
+ def test_volume_create_get_update_delete_from_image(self):
+ self._volume_create_get_update_delete(imageRef=self.
+ config.compute.image_ref)
@attr(type='gate')
- def test_volume_create_get_delete_as_clone(self):
+ def test_volume_create_get_update_delete_as_clone(self):
origin = self.create_volume(size=1,
display_name="Volume Origin")
- self._volume_create_get_delete(source_volid=origin['id'])
+ self._volume_create_get_update_delete(source_volid=origin['id'])
class VolumesGetTestXML(VolumesGetTest):
diff --git a/tempest/common/commands.py b/tempest/common/commands.py
new file mode 100644
index 0000000..4fc85b6
--- /dev/null
+++ b/tempest/common/commands.py
@@ -0,0 +1,76 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# 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
+
+from tempest.openstack.common import log as logging
+
+LOG = logging.getLogger(__name__)
+
+# NOTE(afazekas):
+# These commands assumes the tempest node is the same as
+# the only one service node. all-in-one installation.
+
+
+def sudo_cmd_call(cmd):
+ args = shlex.split(cmd)
+ subprocess_args = {'stdout': subprocess.PIPE,
+ 'stderr': subprocess.STDOUT}
+ try:
+ proc = subprocess.Popen(['/usr/bin/sudo'] + args, **subprocess_args)
+ return proc.communicate()[0]
+ if proc.returncode != 0:
+ LOG.error(cmd + "returned with: " +
+ proc.returncode + "exit status")
+ except subprocess.CalledProcessError as e:
+ LOG.error("command output:\n%s" % e.output)
+
+
+def ip_addr_raw():
+ return sudo_cmd_call("ip a")
+
+
+def ip_route_raw():
+ return sudo_cmd_call("ip r")
+
+
+def ip_ns_raw():
+ return sudo_cmd_call("ip netns list")
+
+
+def iptables_raw(table):
+ return sudo_cmd_call("iptables -v -S -t " + table)
+
+
+def ip_ns_list():
+ return ip_ns_raw().split()
+
+
+def ip_ns_exec(ns, cmd):
+ return sudo_cmd_call(" ".join(("ip netns exec", ns, cmd)))
+
+
+def ip_ns_addr(ns):
+ return ip_ns_exec(ns, "ip a")
+
+
+def ip_ns_route(ns):
+ return ip_ns_exec(ns, "ip r")
+
+
+def iptables_ns(ns, table):
+ return ip_ns_exec(ns, "iptables -v -S -t " + table)
diff --git a/tempest/common/debug.py b/tempest/common/debug.py
new file mode 100644
index 0000000..69c933c
--- /dev/null
+++ b/tempest/common/debug.py
@@ -0,0 +1,41 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# 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.common import commands
+from tempest import config
+
+from tempest.openstack.common import log as logging
+
+LOG = logging.getLogger(__name__)
+
+tables = ['filter', 'nat', 'mangle']
+
+
+def log_ip_ns():
+ if not config.TempestConfig().debug.enable:
+ return
+ LOG.info("Host Addr:\n" + commands.ip_addr_raw())
+ LOG.info("Host Route:\n" + commands.ip_route_raw())
+ for table in ['filter', 'nat', 'mangle']:
+ LOG.info('Host %s table:\n%s', table, commands.iptables_raw(table))
+ ns_list = commands.ip_ns_list()
+ LOG.info("Host ns list" + str(ns_list))
+ for ns in ns_list:
+ LOG.info("ns(%s) Addr:\n%s", ns, commands.ip_ns_addr(ns))
+ LOG.info("ns(%s) Route:\n%s", ns, commands.ip_ns_route(ns))
+ for table in ['filter', 'nat', 'mangle']:
+ LOG.info('ns(%s) table(%s):\n%s', ns, table,
+ commands.iptables_ns(ns, table))
diff --git a/tempest/config.py b/tempest/config.py
index 1b52f5e..8058753 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -628,6 +628,21 @@
for opt in ServiceAvailableGroup:
conf.register_opt(opt, group='service_available')
+debug_group = cfg.OptGroup(name="debug",
+ title="Debug System")
+
+DebugGroup = [
+ cfg.BoolOpt('enable',
+ default=True,
+ help="Enable diagnostic commands"),
+]
+
+
+def register_debug_opts(conf):
+ conf.register_group(debug_group)
+ for opt in DebugGroup:
+ conf.register_opt(opt, group='debug')
+
@singleton
class TempestConfig:
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index de05875..daa7d13 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -17,6 +17,7 @@
# under the License.
from tempest.api.network import common as net_common
+from tempest.common import debug
from tempest.common.utils.data_utils import rand_name
from tempest import config
from tempest.openstack.common import log as logging
@@ -250,10 +251,17 @@
# key-based authentication by cloud-init.
ssh_login = self.config.compute.image_ssh_user
private_key = self.keypairs[self.tenant_id].private_key
- for server, floating_ips in self.floating_ips.iteritems():
- for floating_ip in floating_ips:
- ip_address = floating_ip.floating_ip_address
- self._check_vm_connectivity(ip_address, ssh_login, private_key)
+ try:
+ for server, floating_ips in self.floating_ips.iteritems():
+ for floating_ip in floating_ips:
+ ip_address = floating_ip.floating_ip_address
+ self._check_vm_connectivity(ip_address,
+ ssh_login,
+ private_key)
+ except Exception as exc:
+ LOG.exception(exc)
+ debug.log_ip_ns()
+ raise exc
@attr(type='smoke')
@services('compute', 'network')
diff --git a/tempest/services/compute/json/hosts_client.py b/tempest/services/compute/json/hosts_client.py
index 8093d19..30a3f7b 100644
--- a/tempest/services/compute/json/hosts_client.py
+++ b/tempest/services/compute/json/hosts_client.py
@@ -37,3 +37,10 @@
resp, body = self.get(url)
body = json.loads(body)
return resp, body['hosts']
+
+ def show_host_detail(self, hostname):
+ """Show detail information for the host."""
+
+ resp, body = self.get("os-hosts/%s" % str(hostname))
+ body = json.loads(body)
+ return resp, body['host']
diff --git a/tempest/services/compute/xml/hosts_client.py b/tempest/services/compute/xml/hosts_client.py
index 70aeb48..9743143 100644
--- a/tempest/services/compute/xml/hosts_client.py
+++ b/tempest/services/compute/xml/hosts_client.py
@@ -39,3 +39,11 @@
node = etree.fromstring(body)
body = [xml_to_json(x) for x in node.getchildren()]
return resp, body
+
+ def show_host_detail(self, hostname):
+ """Show detail information for the host."""
+
+ resp, body = self.get("os-hosts/%s" % str(hostname), self.headers)
+ node = etree.fromstring(body)
+ body = [xml_to_json(x) for x in node.getchildren()]
+ return resp, body
diff --git a/tempest/services/identity/json/identity_client.py b/tempest/services/identity/json/identity_client.py
index 47977df..18132ed 100644
--- a/tempest/services/identity/json/identity_client.py
+++ b/tempest/services/identity/json/identity_client.py
@@ -152,6 +152,14 @@
body = json.loads(body)
return resp, body['user']
+ def update_user(self, user_id, **kwargs):
+ """Updates a user."""
+ put_body = json.dumps({'user': kwargs})
+ resp, body = self.put('users/%s' % user_id, put_body,
+ self.headers)
+ body = json.loads(body)
+ return resp, body['user']
+
def get_user(self, user_id):
"""GET a user."""
resp, body = self.get("users/%s" % user_id)
diff --git a/tempest/services/identity/xml/identity_client.py b/tempest/services/identity/xml/identity_client.py
index 7a00b84..9d44826 100644
--- a/tempest/services/identity/xml/identity_client.py
+++ b/tempest/services/identity/xml/identity_client.py
@@ -172,6 +172,18 @@
body = self._parse_body(etree.fromstring(body))
return resp, body
+ def update_user(self, user_id, **kwargs):
+ """Updates a user."""
+ if 'enabled' in kwargs:
+ kwargs['enabled'] = str(kwargs['enabled']).lower()
+ update_user = Element("user", xmlns=XMLNS, **kwargs)
+
+ resp, body = self.put('users/%s' % user_id,
+ str(Document(update_user)),
+ self.headers)
+ body = self._parse_body(etree.fromstring(body))
+ return resp, body
+
def get_user(self, user_id):
"""GET a user."""
resp, body = self.get("users/%s" % user_id, self.headers)
diff --git a/tempest/services/volume/json/volumes_client.py b/tempest/services/volume/json/volumes_client.py
index c35452e..32b6270 100644
--- a/tempest/services/volume/json/volumes_client.py
+++ b/tempest/services/volume/json/volumes_client.py
@@ -85,6 +85,14 @@
body = json.loads(body)
return resp, body['volume']
+ def update_volume(self, volume_id, **kwargs):
+ """Updates the Specified Volume."""
+ put_body = json.dumps({'volume': kwargs})
+ resp, body = self.put('volumes/%s' % volume_id, put_body,
+ self.headers)
+ body = json.loads(body)
+ return resp, body['volume']
+
def delete_volume(self, volume_id):
"""Deletes the Specified Volume."""
return self.delete("volumes/%s" % str(volume_id))
diff --git a/tempest/services/volume/xml/volumes_client.py b/tempest/services/volume/xml/volumes_client.py
index 9fa7a1e..7915637 100644
--- a/tempest/services/volume/xml/volumes_client.py
+++ b/tempest/services/volume/xml/volumes_client.py
@@ -151,6 +151,16 @@
body = xml_to_json(etree.fromstring(body))
return resp, body
+ def update_volume(self, volume_id, **kwargs):
+ """Updates the Specified Volume."""
+ put_body = Element("volume", xmlns=XMLNS_11, **kwargs)
+
+ resp, body = self.put('volumes/%s' % volume_id,
+ str(Document(put_body)),
+ self.headers)
+ body = xml_to_json(etree.fromstring(body))
+ return resp, body
+
def delete_volume(self, volume_id):
"""Deletes the Specified Volume."""
return self.delete("volumes/%s" % str(volume_id))
diff --git a/tools/install_venv.py b/tools/install_venv.py
index 1664b35..f37e0cd 100644
--- a/tools/install_venv.py
+++ b/tools/install_venv.py
@@ -56,7 +56,7 @@
Also, make test will automatically use the virtualenv.
"""
- print help
+ print(help)
def main(argv):
diff --git a/tox.ini b/tox.ini
index 0b57eb2..1b8a0fd 100644
--- a/tox.ini
+++ b/tox.ini
@@ -30,6 +30,7 @@
[testenv:heat-slow]
sitepackages = True
setenv = VIRTUAL_ENV={envdir}
+ OS_TEST_TIMEOUT=1200
# The regex below is used to select heat api/scenario tests tagged as slow.
commands =
sh tools/pretty_tox_serial.sh '(?=.*\[.*\bslow\b.*\])(^tempest\.(api|scenario)\.orchestration) {posargs}'