Merge "Fixes bug 992275-Some new tests to test_floating_ips_actions.py"
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index aa101d3..c7c403c 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -57,6 +57,23 @@
# to build or reach an expected status
build_timeout = 600
+# Run additional tests that use SSH for instance validation?
+# This requires the instances be routable from the host
+# executing the tests
+run_ssh = false
+
+# Name of a user used to authenticated to an instance
+ssh_user = {$SSH_USER}
+
+# Network id used for SSH (public, private, etc)
+network_for_ssh = {$SSH_NETWORK}
+
+# IP version of the address used for SSH
+ip_version_for_ssh = {$SSH_IP_VERSION}
+
+# Number of seconds to wait to authenticate to an instance
+ssh_timeout = 300
+
# The type of endpoint for a Compute API service. Unless you have a
# custom Keystone service catalog implementation, you probably want to leave
# this value as "compute"
diff --git a/tempest/common/rest_client.py b/tempest/common/rest_client.py
index a18e563..ea52479 100644
--- a/tempest/common/rest_client.py
+++ b/tempest/common/rest_client.py
@@ -130,25 +130,20 @@
for ep in auth_data['serviceCatalog']:
if ep["type"] == service:
mgmt_url = ep['endpoints'][self.region][self.endpoint_url]
- # See LP#920817. The tenantId is *supposed*
- # to be returned for each endpoint accorsing to the
- # Keystone spec. But... it isn't, so we have to parse
- # the tenant ID out of hte public URL :(
- tenant_id = mgmt_url.split('/')[-1]
+ tenant_id = auth_data['token']['tenant']['id']
break
if mgmt_url == None:
raise exceptions.EndpointNotFound(service)
- if mgmt_url.endswith(tenant_id):
- return token, mgmt_url
- else:
- #TODO (dwalleck): This is a horrible stopgap.
- #Need to join strings more cleanly
- temp = mgmt_url.rsplit('/')
- service_url = temp[0] + '//' + temp[2] + '/' + temp[3] + '/'
- management_url = service_url + tenant_id
- return token, management_url
+ if service == 'network':
+ # Keystone does not return the correct endpoint for
+ # quantum. Handle this separately.
+ mgmt_url = mgmt_url + self.config.network.api_version + \
+ "/tenants/" + tenant_id
+
+ return token, mgmt_url
+
elif resp.status == 401:
raise exceptions.AuthenticationFailure(user=user,
password=password)
diff --git a/tempest/common/ssh.py b/tempest/common/ssh.py
index 2f1d96b..f43ebd9 100644
--- a/tempest/common/ssh.py
+++ b/tempest/common/ssh.py
@@ -1,6 +1,7 @@
import time
import socket
import warnings
+from tempest import exceptions
with warnings.catch_warnings():
warnings.simplefilter("ignore")
@@ -9,7 +10,7 @@
class Client(object):
- def __init__(self, host, username, password, timeout=300):
+ def __init__(self, host, username, password, timeout=60):
self.host = host
self.username = username
self.password = password
@@ -26,8 +27,7 @@
while not self._is_timed_out(self.timeout, _start_time):
try:
ssh.connect(self.host, username=self.username,
- password=self.password, look_for_keys=False,
- timeout=20)
+ password=self.password, timeout=20)
_timeout = False
break
except socket.error:
@@ -36,7 +36,9 @@
time.sleep(15)
continue
if _timeout:
- raise socket.error("SSH connect timed out")
+ raise exceptions.SSHTimeout(host=self.host,
+ user=self.username,
+ password=self.password)
return ssh
def _is_timed_out(self, timeout, start_time):
diff --git a/tempest/common/utils/__init__.py b/tempest/common/utils/__init__.py
index e69de29..b9829a3 100644
--- a/tempest/common/utils/__init__.py
+++ b/tempest/common/utils/__init__.py
@@ -0,0 +1,4 @@
+LAST_REBOOT_TIME_FORMAT = '%Y-%m-%d %H:%M'
+PING_IPV4_COMMAND = 'ping -c 3 '
+PING_IPV6_COMMAND = 'ping6 -c 3 '
+PING_PACKET_LOSS_REGEX = '(\d{1,3})\.?\d*\% packet loss'
diff --git a/tempest/common/utils/linux/__init__.py b/tempest/common/utils/linux/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/common/utils/linux/__init__.py
diff --git a/tempest/common/utils/linux/remote_client.py b/tempest/common/utils/linux/remote_client.py
new file mode 100644
index 0000000..27f8fd3
--- /dev/null
+++ b/tempest/common/utils/linux/remote_client.py
@@ -0,0 +1,48 @@
+from tempest.common.ssh import Client
+from tempest.config import TempestConfig
+from tempest.exceptions import SSHTimeout, ServerUnreachable
+
+
+class RemoteClient():
+
+ def __init__(self, server, username, password):
+ ssh_timeout = TempestConfig().compute.ssh_timeout
+ network = TempestConfig().compute.network_for_ssh
+ ip_version = TempestConfig().compute.ip_version_for_ssh
+ addresses = server['addresses'][network]
+
+ for address in addresses:
+ if address['version'] == ip_version:
+ ip_address = address['addr']
+ break
+
+ if ip_address is None:
+ raise ServerUnreachable()
+
+ self.ssh_client = Client(ip_address, username, password, ssh_timeout)
+ if not self.ssh_client.test_connection_auth():
+ raise SSHTimeout()
+
+ def can_authenticate(self):
+ # Re-authenticate
+ return self.ssh_client.test_connection_auth()
+
+ def hostname_equals_servername(self, expected_hostname):
+ # Get hostname using command "hostname"
+ actual_hostname = self.ssh_client.exec_command("hostname").rstrip()
+ return expected_hostname == actual_hostname
+
+ def get_files(self, path):
+ # Return a list of comma seperated files
+ command = "ls -m " + path
+ return self.ssh_client.exec_command(command).rstrip('\n').split(', ')
+
+ def get_ram_size_in_mb(self):
+ output = self.ssh_client.exec_command('free -m | grep Mem')
+ if output:
+ return output.split()[1]
+
+ def get_number_of_vcpus(self):
+ command = 'cat /proc/cpuinfo | grep processor | wc -l'
+ output = self.ssh_client.exec_command(command)
+ return int(output)
diff --git a/tempest/config.py b/tempest/config.py
index d4a0c03..0a76ce5 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -165,6 +165,31 @@
return float(self.get("build_timeout", 300))
@property
+ def run_ssh(self):
+ """Does the test environment support snapshots?"""
+ return self.get("run_ssh", 'false').lower() != 'false'
+
+ @property
+ def ssh_user(self):
+ """User name used to authenticate to an instance."""
+ return self.get("ssh_user", "root")
+
+ @property
+ def ssh_timeout(self):
+ """Timeout in seconds to wait for authentcation to succeed."""
+ return float(self.get("ssh_timeout", 300))
+
+ @property
+ def network_for_ssh(self):
+ """Network used for SSH connections."""
+ return self.get("network_for_ssh", "public")
+
+ @property
+ def ip_version_for_ssh(self):
+ """IP version used for SSH connections."""
+ return int(self.get("ip_version_for_ssh", 4))
+
+ @property
def catalog_type(self):
"""Catalog type of the Compute service."""
return self.get("catalog_type", 'compute')
diff --git a/tempest/exceptions.py b/tempest/exceptions.py
index b49e9e6..29ddeeb 100644
--- a/tempest/exceptions.py
+++ b/tempest/exceptions.py
@@ -47,6 +47,10 @@
message = "Server %(server_id)s failed to build and is in ERROR status"
+class VolumeBuildErrorException(TempestException):
+ message = "Volume %(volume_id)s failed to build and is in ERROR status"
+
+
class BadRequest(TempestException):
message = "Bad request"
@@ -75,3 +79,12 @@
class Duplicate(TempestException):
message = "An object with that identifier already exists"
+
+
+class SSHTimeout(TempestException):
+ message = ("Connection to the %(host)s via SSH timed out.\n"
+ "User: %(user)s, Password: %(password)s")
+
+
+class ServerUnreachable(TempestException):
+ message = "The server is not reachable via the configured network"
diff --git a/tempest/services/nova/json/servers_client.py b/tempest/services/nova/json/servers_client.py
index 53b2d6f..b732b41 100644
--- a/tempest/services/nova/json/servers_client.py
+++ b/tempest/services/nova/json/servers_client.py
@@ -155,6 +155,24 @@
message += ' Current status: %s.' % server_status
raise exceptions.TimeoutException(message)
+ def wait_for_server_termination(self, server_id):
+ """Waits for server to reach termination"""
+ start_time = int(time.time())
+ while True:
+ try:
+ resp, body = self.get_server(server_id)
+ except exceptions.NotFound:
+ return
+
+ server_status = body['status']
+ if server_status == 'ERROR':
+ raise exceptions.BuildErrorException
+
+ if int(time.time()) - start_time >= self.build_timeout:
+ raise exceptions.TimeoutException
+
+ time.sleep(self.build_interval)
+
def list_addresses(self, server_id):
"""Lists all addresses for a server"""
resp, body = self.get("servers/%s/ips" % str(server_id))
diff --git a/tempest/services/nova/json/volumes_client.py b/tempest/services/nova/json/volumes_client.py
index ce212ff..165640f 100644
--- a/tempest/services/nova/json/volumes_client.py
+++ b/tempest/services/nova/json/volumes_client.py
@@ -83,7 +83,7 @@
resp, body = self.get_volume(volume_id)
volume_status = body['status']
if volume_status == 'error':
- raise exceptions.BuildErrorException(volume_id=volume_id)
+ raise exceptions.VolumeBuildErrorException(volume_id=volume_id)
if int(time.time()) - start >= self.build_timeout:
message = 'Volume %s failed to reach %s status within '\
diff --git a/tempest/tests/base_compute_test.py b/tempest/tests/base_compute_test.py
index 216f506..2436bb0 100644
--- a/tempest/tests/base_compute_test.py
+++ b/tempest/tests/base_compute_test.py
@@ -20,6 +20,7 @@
config = os.config
build_interval = config.compute.build_interval
build_timeout = config.compute.build_timeout
+ ssh_user = config.compute.ssh_user
# Validate reference data exists
# If not, attempt to auto-configure
diff --git a/tempest/tests/compute/test_create_server.py b/tempest/tests/compute/test_create_server.py
new file mode 100644
index 0000000..c51afce
--- /dev/null
+++ b/tempest/tests/compute/test_create_server.py
@@ -0,0 +1,81 @@
+import base64
+import unittest2 as unittest
+from nose.plugins.attrib import attr
+from tempest import openstack
+import tempest.config
+from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils.linux.remote_client import RemoteClient
+from tempest.tests.base_compute_test import BaseComputeTest
+
+
+class ServersTest(BaseComputeTest):
+
+ run_ssh = tempest.config.TempestConfig().compute.run_ssh
+
+ @classmethod
+ def setUpClass(cls):
+ cls.meta = {'hello': 'world'}
+ cls.accessIPv4 = '1.1.1.1'
+ cls.accessIPv6 = '::babe:220.12.22.2'
+ cls.name = rand_name('server')
+ file_contents = 'This is a test file.'
+ personality = [{'path': '/etc/test.txt',
+ 'contents': base64.b64encode(file_contents)}]
+ cls.client = cls.servers_client
+ cls.resp, cls.server_initial = cls.client.create_server(cls.name,
+ cls.image_ref,
+ cls.flavor_ref,
+ meta=cls.meta,
+ accessIPv4=cls.accessIPv4,
+ accessIPv6=cls.accessIPv6,
+ personality=personality)
+ 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'])
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.client.delete_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_created_server_fields(self):
+ """Verify the specified server attributes are set correctly"""
+
+ self.assertEqual(self.accessIPv4, self.server['accessIPv4'])
+ self.assertEqual(self.accessIPv6, self.server['accessIPv6'])
+ self.assertEqual(self.name, self.server['name'])
+ self.assertEqual(self.image_ref, self.server['image']['id'])
+ self.assertEqual(str(self.flavor_ref), self.server['flavor']['id'])
+ self.assertEqual(self.meta, self.server['metadata'])
+
+ @attr(type='positive')
+ @unittest.skipIf(not run_ssh, 'Instance validation tests are disabled.')
+ 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())
+
+ @attr(type='positive')
+ @unittest.skipIf(not run_ssh, 'Instance validation tests are disabled.')
+ 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())
+
+ @attr(type='positive')
+ @unittest.skipIf(not run_ssh, 'Instance validation tests are disabled.')
+ 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))
diff --git a/tempest/tests/identity/test_tenants.py b/tempest/tests/identity/test_tenants.py
index 92feea8..0313ab3 100644
--- a/tempest/tests/identity/test_tenants.py
+++ b/tempest/tests/identity/test_tenants.py
@@ -1,4 +1,4 @@
-import nose
+import unittest2 as unittest
from tempest import exceptions
from tempest.common.utils.data_utils import rand_name
from base_admin_test import BaseAdminTest
@@ -10,9 +10,6 @@
def setUpClass(cls):
super(TenantsTest, cls).setUpClass()
- if not cls.client.has_admin_extensions():
- raise nose.SkipTest("Admin extensions disabled")
-
for _ in xrange(5):
resp, tenant = cls.client.create_tenant(rand_name('tenant-'))
cls.data.tenants.append(tenant)
@@ -30,6 +27,18 @@
self.assertEqual(len(found), len(self.data.tenants))
self.assertTrue(resp['status'].startswith('2'))
+ def test_list_tenants_by_unauthorized_user(self):
+ """Non-admin user should not be able to list tenants"""
+ self.assertRaises(exceptions.Unauthorized,
+ self.non_admin_client.list_tenants)
+
+ def test_list_tenant_request_without_token(self):
+ """Request to list tenants without a valid token should fail"""
+ token = self.client.get_auth()
+ self.client.delete_token(token)
+ self.assertRaises(exceptions.Unauthorized, self.client.list_tenants)
+ self.client.clear_auth()
+
def test_tenant_delete(self):
"""Create several tenants and delete them"""
tenants = []
@@ -48,6 +57,28 @@
self.assertTrue(any(found_1), 'Tenants not created')
self.assertFalse(any(found_2), 'Tenants failed to delete')
+ def test_tenant_delete_by_unauthorized_user(self):
+ """Non-admin user should not be able to delete a tenant"""
+ tenant_name = rand_name('tenant-')
+ resp, tenant = self.client.create_tenant(tenant_name)
+ self.assertRaises(exceptions.Unauthorized,
+ self.non_admin_client.delete_tenant, tenant['id'])
+
+ def test_tenant_delete_request_without_token(self):
+ """Request to delete a tenant without a valid token should fail"""
+ tenant_name = rand_name('tenant-')
+ resp, tenant = self.client.create_tenant(tenant_name)
+ token = self.client.get_auth()
+ self.client.delete_token(token)
+ self.assertRaises(exceptions.Unauthorized, self.client.delete_tenant,
+ tenant['id'])
+ self.client.clear_auth()
+
+ def test_delete_non_existent_tenant(self):
+ """Attempt to delete a non existent tenant should fail"""
+ self.assertRaises(exceptions.NotFound, self.client.delete_tenant,
+ 'junk_tenant_123456abc')
+
def test_tenant_create_with_description(self):
"""Create tenant with a description"""
tenant_name = rand_name('tenant-')
@@ -109,6 +140,34 @@
if tenant1_id:
self.client.delete_tenant(tenant1_id)
+ def test_create_tenant_by_unauthorized_user(self):
+ """Non-admin user should not be authorized to create a tenant"""
+ tenant_name = rand_name('tenant-')
+ self.assertRaises(exceptions.Unauthorized,
+ self.non_admin_client.create_tenant, tenant_name)
+
+ def test_create_tenant_request_without_token(self):
+ """Create tenant request without a token should not be authorized"""
+ tenant_name = rand_name('tenant-')
+ token = self.client.get_auth()
+ self.client.delete_token(token)
+ self.assertRaises(exceptions.Unauthorized, self.client.create_tenant,
+ tenant_name)
+ self.client.clear_auth()
+
+ @unittest.skip("Until Bug 987121 is fixed")
+ def test_create_tenant_with_empty_name(self):
+ """Tenant name should not be empty"""
+ self.assertRaises(exceptions.BadRequest, self.client.create_tenant,
+ name='')
+
+ @unittest.skip("Until Bug 966249 is fixed")
+ def test_create_tenants_name_length_over_64(self):
+ """Tenant name length should not be greater than 64 characters"""
+ tenant_name = 'a' * 64
+ self.assertRaises(exceptions.BadRequest, self.client.create_tenant,
+ tenant_name)
+
def test_tenant_update_name(self):
"""Update name attribute of a tenant"""
t_name1 = rand_name('tenant-')
diff --git a/tempest/tests/test_authorization.py b/tempest/tests/test_authorization.py
index e66446d..147f488 100644
--- a/tempest/tests/test_authorization.py
+++ b/tempest/tests/test_authorization.py
@@ -16,6 +16,7 @@
cls.os = openstack.Manager()
cls.client = cls.os.servers_client
cls.images_client = cls.os.images_client
+ cls.keypairs_client = cls.os.keypairs_client
cls.config = cls.os.config
cls.image_ref = cls.config.compute.image_ref
cls.flavor_ref = cls.config.compute.flavor_ref
@@ -37,6 +38,7 @@
cls.other_manager = openstack.AltManager()
cls.other_client = cls.other_manager.servers_client
cls.other_images_client = cls.other_manager.images_client
+ cls.other_keypairs_client = cls.other_manager.keypairs_client
except exceptions.AuthenticationFailure:
# multi_user is already set to false, just fall through
pass
@@ -56,11 +58,16 @@
cls.images_client.wait_for_image_status(image_id, 'ACTIVE')
resp, cls.image = cls.images_client.get_image(image_id)
+ cls.keypairname = rand_name('keypair')
+ resp, keypair = \
+ cls.keypairs_client.create_keypair(cls.keypairname)
+
@classmethod
def tearDownClass(cls):
if cls.multi_user:
cls.client.delete_server(cls.server['id'])
cls.images_client.delete_image(cls.image['id'])
+ cls.keypairs_client.delete_keypair(cls.keypairname)
@raises(exceptions.NotFound)
@attr(type='negative')
@@ -160,3 +167,57 @@
finally:
# Reset the base_url...
self.other_client.base_url = saved_base_url
+
+ @raises(exceptions.BadRequest)
+ @attr(type='negative')
+ @utils.skip_unless_attr('multi_user', 'Second user not configured')
+ def test_create_keypair_in_another_user_tenant(self):
+ """
+ A create keypair request should fail if the tenant id does not match
+ the current user
+ """
+ #POST keypair with other user tenant
+ k_name = rand_name('keypair-')
+ self.other_keypairs_client._set_auth()
+ self.saved_base_url = self.other_keypairs_client.base_url
+ try:
+ # Change the base URL to impersonate another user
+ self.other_keypairs_client.base_url = self.keypairs_client.base_url
+ resp = {}
+ resp['status'] = None
+ resp, _ = self.other_keypairs_client.create_keypair(k_name)
+ finally:
+ # Reset the base_url...
+ self.other_keypairs_client.base_url = self.saved_base_url
+ if (resp['status'] != None):
+ resp, _ = self.other_keypairs_client.delete_keypair(k_name)
+ self.fail("Create keypair request should not happen if the"
+ " tenant id does not match the current user")
+
+ @raises(exceptions.NotFound)
+ @attr(type='negative')
+ @utils.skip_unless_attr('multi_user', 'Second user not configured')
+ def test_get_keypair_of_other_account_fails(self):
+ """A GET request for another user's keypair should fail"""
+ self.other_keypairs_client.get_keypair(self.keypairname)
+
+ @raises(exceptions.NotFound)
+ @attr(type='negative')
+ @utils.skip_unless_attr('multi_user', 'Second user not configured')
+ def test_delete_keypair_of_other_account_fails(self):
+ """A DELETE request for another user's keypair should fail"""
+ self.other_keypairs_client.delete_keypair(self.keypairname)
+
+ @raises(exceptions.NotFound)
+ @attr(type='negative')
+ @utils.skip_unless_attr('multi_user', 'Second user not configured')
+ def test_get_image_for_other_account_fails(self):
+ """A GET request for an image on another user's account should fail"""
+ self.other_images_client.get_image(self.image['id'])
+
+ @raises(exceptions.NotFound)
+ @attr(type='negative')
+ @utils.skip_unless_attr('multi_user', 'Second user not configured')
+ def test_delete_image_for_other_account_fails(self):
+ """A DELETE request for another user's image should fail"""
+ self.other_images_client.delete_image(self.image['id'])
diff --git a/tempest/tests/test_images.py b/tempest/tests/test_images.py
index 33020d6..65fd4be 100644
--- a/tempest/tests/test_images.py
+++ b/tempest/tests/test_images.py
@@ -6,6 +6,7 @@
import tempest.config
from tempest import openstack
from tempest.common.utils import data_utils
+from tempest import exceptions
class ImagesTest(BaseComputeTest):
@@ -79,3 +80,38 @@
self.client.wait_for_image_status(image_id, 'ACTIVE')
self.client.delete_image(image_id)
self.fail("Should not create snapshot from deleted instance!")
+
+ @attr(type='negative')
+ def test_create_image_from_invalid_server(self):
+ """An image should not be created with invalid server id"""
+ try:
+ # Create a new image with invalid server id
+ name = rand_name('image')
+ meta = {'image_type': 'test'}
+ resp = {}
+ resp['status'] = None
+ resp, body = self.client.create_image('!@#$%^&*()', name, meta)
+
+ except exceptions.NotFound:
+ pass
+
+ finally:
+ if (resp['status'] != None):
+ image_id = data_utils.parse_image_id(resp['location'])
+ resp, _ = self.client.delete_image(image_id)
+ self.fail("An image should not be created"
+ " with invalid server id")
+
+ @attr(type='negative')
+ def test_delete_image_with_invalid_image_id(self):
+ """An image should not be deleted with invalid image id"""
+ try:
+ # Delete an image with invalid image id
+ resp, _ = self.client.delete_image('!@$%^&*()')
+
+ except exceptions.NotFound:
+ pass
+
+ else:
+ self.fail("DELETE image request should rasie NotFound exception"
+ "when requested with invalid image")
diff --git a/tempest/tests/test_keypairs.py b/tempest/tests/test_keypairs.py
index fde9de1..d3ba213 100644
--- a/tempest/tests/test_keypairs.py
+++ b/tempest/tests/test_keypairs.py
@@ -62,6 +62,29 @@
self.assertEqual(202, resp.status)
@attr(type='smoke')
+ @unittest.skip("Skipped until the Bug #980688 is resolved")
+ def test_get_keypair_detail(self):
+ """Keypair should be created, Got details by name and deleted"""
+ k_name = rand_name('keypair-')
+ resp, keypair = self.client.create_keypair(k_name)
+ try:
+ resp, keypair_detail = self.client.get_keypair(k_name)
+ self.assertEqual(200, resp.status)
+ self.assertTrue('name' in keypair_detail)
+ self.assertTrue('public_key' in keypair_detail)
+ self.assertEqual(keypair_detail['name'], k_name,
+ "The created keypair name is not equal to requested name")
+ public_key = keypair_detail['public_key']
+ self.assertTrue(public_key is not None,
+ "Field public_key is empty or not found.")
+ except:
+ self.fail("GET keypair details requested by keypair name"
+ " has failed")
+ finally:
+ resp, _ = self.client.delete_keypair(k_name)
+ self.assertEqual(202, resp.status)
+
+ @attr(type='smoke')
def test_keypair_create_with_pub_key(self):
"""Keypair should be created with a given public key"""
k_name = rand_name('keypair-')
diff --git a/tempest/tests/test_list_servers_negative.py b/tempest/tests/test_list_servers_negative.py
new file mode 100644
index 0000000..18030b7
--- /dev/null
+++ b/tempest/tests/test_list_servers_negative.py
@@ -0,0 +1,409 @@
+import re
+import sys
+import unittest2 as unittest
+from tempest import exceptions
+from base_compute_test import BaseComputeTest
+from tempest import openstack
+from tempest.common.utils.data_utils import rand_name
+
+
+class ServerDetailsNegativeTest(BaseComputeTest):
+
+ @classmethod
+ def setUpClass(cls):
+ cls.client = cls.servers_client
+ cls.servers = []
+
+ # Verify the alternate user is configured and not the same as the first
+ cls.user1 = cls.config.compute.username
+ cls.user2 = cls.config.compute.alt_username
+ cls.user2_password = cls.config.compute.alt_password
+ cls.user2_tenant_name = cls.config.compute.alt_tenant_name
+ cls.multi_user = False
+
+ if (not None in (cls.user2, cls.user2_password, cls.user2_tenant_name)
+ and cls.user1 != cls.user2):
+
+ try:
+ cls.alt_manager = openstack.AltManager()
+ cls.alt_client = cls.alt_manager.servers_client
+ except exceptions.AuthenticationFailure:
+ # multi_user is already set to false, just fall through
+ pass
+ else:
+ cls.multi_user = True
+
+ @classmethod
+ def tearDownClass(cls):
+ """Terminate all running instances in nova"""
+ try:
+ resp, body = cls.client.list_servers()
+ for server in body['servers']:
+ resp, body = cls.client.delete_server(server['id'])
+ except exceptions.NotFound:
+ pass
+
+ def tearDown(self):
+ """Terminate instances created by tests"""
+ try:
+ for server in self.servers:
+ resp, body = self.client.delete_server(server)
+ if resp['status'] == '204':
+ self.client.wait_for_server_termination(server)
+ except exceptions.NotFound:
+ pass
+
+ def get_active_servers(self, server_count):
+ """Returns active test instances to calling test methods"""
+ resp, body = self.client.list_servers_with_detail()
+ servers = body['servers']
+ active_servers = [server for server in servers if server['status'] ==
+ 'ACTIVE']
+ num_of_active_servers = len(active_servers)
+
+ # Check if we already have enough active servers
+ if active_servers and num_of_active_servers >= server_count:
+ return active_servers[0:server_count]
+
+ # Otherwise create the remaining shortfall of servers
+ servers_needed = server_count - num_of_active_servers
+
+ for i in range(0, servers_needed):
+ instance = self.create_instance()
+ active_servers.append(instance)
+
+ return active_servers
+
+ def create_instance(self, image_id=None):
+ name = rand_name('test-vm-')
+ flavor = self.flavor_ref
+
+ if not image_id:
+ image_id = self.image_ref
+
+ body, server = self.client.create_server(name, image_id, flavor)
+ self.client.wait_for_server_status(server['id'], 'ACTIVE')
+ self.servers.append(server['id'])
+ return server
+
+ def test_list_servers_when_no_servers_running(self):
+ """Return an empty list when there are no active servers"""
+ # Delete Active servers
+ try:
+ resp, body = self.client.list_servers()
+ for server in body['servers']:
+ resp, body = self.client.delete_server(server['id'])
+ self.client.wait_for_server_termination(server['id'])
+ except exceptions.NotFound:
+ pass
+ # Verify empty list
+ resp, body = self.client.list_servers()
+ servers = body['servers']
+ self.assertEqual('200', resp['status'])
+ self.assertEqual([], servers)
+
+ def test_list_servers_with_a_deleted_server(self):
+ """Create and delete a server and verify empty list"""
+ server = self.get_active_servers(1)[0]
+
+ # Delete the server
+ self.client.delete_server(server['id'])
+ self.client.wait_for_server_termination(server['id'])
+
+ # List servers and verify server not returned
+ resp, body = self.client.list_servers()
+ servers = body['servers']
+ actual = [srv for srv in servers if srv['id'] == server['id']]
+ self.assertEqual('200', resp['status'])
+ self.assertEqual([], actual)
+
+ def test_list_servers_by_existing_image(self):
+ """Server list is returned for a specific image and snapshot images"""
+ try:
+ base_server = self.get_active_servers(1)[0]
+
+ # Create a snapshot of the server
+ snapshot_name = "%s_snapshot" % base_server['id']
+ resp, body = self.client.create_image(base_server['id'],
+ snapshot_name)
+ snapshot_url = resp['location']
+ match = re.search('/images/(?P<image_id>.+)', snapshot_url)
+ self.assertIsNotNone(match)
+ snapshot_id = match.groupdict()['image_id']
+
+ self.images_client.wait_for_image_status(snapshot_id, 'ACTIVE')
+
+ # Create a server from the snapshot
+ snap_server = self.create_instance(image_id=snapshot_id)
+ self.servers.append(snap_server['id'])
+
+ # List base servers by image
+ resp, body = self.client.list_servers({'image': self.image_ref})
+ servers = body['servers']
+ self.assertEqual('200', resp['status'])
+ self.assertIn(base_server['id'], [server['id'] for server in
+ servers])
+ self.assertTrue(len(body['servers']) > 0)
+
+ # List snapshotted server by image
+ resp, body = self.client.list_servers({'image': snapshot_id})
+ servers = body['servers']
+ self.assertEqual('200', resp['status'])
+ self.assertIn(snap_server['id'], [server['id'] for server in
+ servers])
+ self.assertEqual(1, len(body['servers']))
+
+ finally:
+ self.images_client.delete_image(snapshot_id)
+
+ @unittest.skip("Until Bug 1002911 is fixed")
+ def test_list_servers_by_non_existing_image(self):
+ """Listing servers for a non existing image raises error"""
+ self.assertRaises(exceptions.NotFound, self.client.list_servers,
+ {'image': '1234abcd-zzz0-aaa9-ppp3-0987654abcde'})
+
+ @unittest.skip("Until Bug 1002911 is fixed")
+ def test_list_servers_by_image_pass_overlimit_image(self):
+ """Return an error while listing server with too large image id"""
+ self.assertRaises(exceptions.OverLimit, self.client.list_servers,
+ {'image': sys.maxint + 1})
+
+ @unittest.skip("Until Bug 1002911 is fixed")
+ def test_list_servers_by_image_pass_negative_id(self):
+ """Return an error while listing server with negative image id"""
+ self.assertRaises(exceptions.BadRequest, self.client.list_servers,
+ {'image': -1})
+
+ def test_list_servers_by_existing_flavor(self):
+ """List servers by flavor"""
+ self.get_active_servers(1)
+
+ resp, body = self.client.list_servers({'flavor': self.flavor_ref})
+ self.assertEqual('200', resp['status'])
+ self.assertTrue(len(body['servers']) > 0)
+
+ @unittest.skip("Until Bug 1002918 is fixed")
+ def test_list_servers_by_non_existing_flavor(self):
+ """Return an error while listing server by non existing flavor"""
+ self.assertRaises(exceptions.NotFound, self.client.list_servers,
+ {'flavor': 1234})
+
+ @unittest.skip("Until Bug 1002918 is fixed")
+ def test_list_servers_by_flavor_pass_overlimit_flavor(self):
+ """Return an error while listing server with too large flavor value"""
+ self.assertRaises(exceptions.OverLimit, self.client.list_servers,
+ {'flavor': sys.maxint + 1})
+
+ @unittest.skip("Until Bug 1002918 is fixed")
+ def test_list_servers_by_flavor_pass_negative_value(self):
+ """Return an error while listing server with negative flavor value"""
+ self.assertRaises(exceptions.BadRequest, self.client.list_servers,
+ {'flavor': -1})
+
+ def test_list_servers_by_server_name(self):
+ """Returns a list of servers containing an existing server name"""
+ server_name = rand_name('test-vm-')
+ resp, server = self.client.create_server(server_name, self.image_ref,
+ self.flavor_ref)
+ self.servers.append(server['id'])
+
+ resp, body = self.client.list_servers({'name': server_name})
+ self.assertEqual('200', resp['status'])
+ self.assertEqual(server_name, body['servers'][0]['name'])
+
+ @unittest.skip("Until Bug 1002892 is fixed")
+ def test_list_servers_by_non_existing_server_name(self):
+ """Return an error while listing for a non existent server"""
+ resp, body = self.client.list_servers({'name': 'junk_serv_100'})
+ self.assertRaises(exceptions.NotFound, self.client.list_servers,
+ {'name': 'junk_serv_100'})
+
+ @unittest.skip("Until Bug 1002892 is fixed")
+ def test_list_servers_by_server_name_empty(self):
+ """Return an error when an empty server name is passed"""
+ self.assertRaises(exceptions.BadRequest, self.client.list_servers,
+ {'name': ''})
+
+ @unittest.skip("Until Bug 1002892 is fixed")
+ def test_list_servers_by_server_name_too_large(self):
+ """Return an error for a very large value for server name listing"""
+ self.assertRaises(exceptions.OverLimit, self.client.list_servers,
+ {'name': 'a' * 65})
+
+ @unittest.skip("Until Bug 1002892 is fixed")
+ def test_list_servers_by_name_pass_numeric_name(self):
+ """Return an error for a numeric server name listing"""
+ self.assertRaises(exceptions.BadRequest, self.client.list_servers,
+ {'name': 99})
+
+ def test_list_servers_by_active_status(self):
+ """Return a listing of servers by active status"""
+ self.create_instance()
+ resp, body = self.client.list_servers({'status': 'ACTIVE'})
+ self.assertEqual('200', resp['status'])
+ self.assertTrue(len(body['servers']) > 0)
+
+ def test_list_servers_by_building_status(self):
+ """Return a listing of servers in build state"""
+ server_name = rand_name('test-vm-')
+ resp, server = self.client.create_server(server_name, self.image_ref,
+ self.flavor_ref)
+ self.servers.append(server['id'])
+ resp, body = self.client.list_servers({'status': 'BUILD'})
+ self.assertEqual('200', resp['status'])
+ self.assertEqual(1, len(body['servers']))
+ self.assertEqual(server['id'], body['servers'][0]['id'])
+
+ self.client.wait_for_server_status(server['id'], 'ACTIVE')
+
+ def test_list_servers_status_is_invalid(self):
+ """Return an error when invalid status is specified"""
+ self.assertRaises(exceptions.BadRequest, self.client.list_servers,
+ {'status': 'DEAD'})
+
+ def test_list_servers_pass_numeric_status(self):
+ """Return an error when a numeric value for status is specified"""
+ self.assertRaises(exceptions.BadRequest, self.client.list_servers,
+ {'status': 1})
+
+ def test_list_servers_by_limits(self):
+ """List servers by specifying limits"""
+ self.get_active_servers(2)
+ resp, body = self.client.list_servers({'limit': 1})
+ self.assertEqual('200', resp['status'])
+ self.assertEqual(1, len(body['servers']))
+
+ def test_list_servers_by_limits_greater_than_actual_count(self):
+ """List servers by specifying a greater value for limit"""
+ self.get_active_servers(2)
+ resp, body = self.client.list_servers({'limit': 100})
+ self.assertEqual('200', resp['status'])
+ self.assertTrue(len(body['servers']) >= 2)
+
+ def test_list_servers_by_limits_pass_string(self):
+ """Return an error if a string value is passed for limit"""
+ self.assertRaises(exceptions.BadRequest, self.client.list_servers,
+ {'limit': 'testing'})
+
+ def test_list_servers_by_limits_pass_negative_value(self):
+ """Return an error if a negative value for limit is passed"""
+ self.assertRaises(exceptions.BadRequest, self.client.list_servers,
+ {'limit': -1})
+
+ @unittest.skip("Until Bug 1002924 is fixed")
+ def test_list_servers_by_limits_pass_overlimit_value(self):
+ """Return an error if too large value for limit is passed"""
+ self.assertRaises(exceptions.OverLimit, self.client.list_servers,
+ {'limit': sys.maxint + 1})
+
+ def test_list_servers_by_changes_since(self):
+ """Servers are listed by specifying changes-since date"""
+ self.get_active_servers(2)
+ resp, body = self.client.list_servers(
+ {'changes-since': '2011-01-01T12:34:00Z'})
+ self.assertEqual('200', resp['status'])
+ self.assertTrue(len(body['servers']) >= 2)
+
+ def test_list_servers_by_changes_since_invalid_date(self):
+ """Return an error when invalid date format is passed"""
+ self.assertRaises(exceptions.BadRequest, self.client.list_servers,
+ {'changes-since': '2011/01/01'})
+
+ @unittest.skip("Until Bug 1002926 is fixed")
+ def test_list_servers_by_changes_since_future_date(self):
+ """Return an error when a date in the future is passed"""
+ self.assertRaises(exceptions.BadRequest, self.client.list_servers,
+ {'changes-since': '2051-01-01T12:34:00Z'})
+
+ @unittest.skip("Until Bug 1002935 is fixed")
+ def test_list_servers_list_another_tenant_servers(self):
+ """Return an error when a user lists servers in another tenant"""
+ # Create a server by a user in it's tenant
+ server_name = rand_name('test-vm-')
+ resp, server = self.client.create_server(server_name, self.image_ref,
+ self.flavor_ref)
+ self.servers.append(server['id'])
+
+ # List the servers by alternate user in the base user's tenant
+ self.assertRaises(exceptions.NotFound, self.alt_client.list_servers,
+ {'name': server_name})
+
+ def test_list_servers_detail_when_no_servers_running(self):
+ """Return an empty list for servers detail when no active servers"""
+ # Delete active servers
+ try:
+ resp, body = self.client.list_servers()
+ for server in body['servers']:
+ resp, body = self.client.delete_server(server['id'])
+ self.client.wait_for_server_termination(server['id'])
+ except exceptions.NotFound:
+ pass
+ # Verify empty list
+ resp, body = self.client.list_servers_with_detail()
+ self.assertEqual('200', resp['status'])
+ servers = body['servers']
+ actual = [srv for srv in servers if srv['status'] == 'ACTIVE']
+ self.assertEqual([], actual)
+
+ def test_list_servers_detail_server_is_building(self):
+ """Server in Build state is listed"""
+ server_name = rand_name('test-vm-')
+ resp, server = self.client.create_server(server_name, self.image_ref,
+ self.flavor_ref)
+
+ self.servers.append(server['id'])
+ resp, body = self.client.list_servers_with_detail()
+ self.assertEqual('200', resp['status'])
+ self.assertEqual('BUILD', body['servers'][0]['status'])
+
+ def test_list_servers_detail_server_is_deleted(self):
+ """Server details are not listed for a deleted server"""
+ server = self.get_active_servers(1)[0]
+
+ self.client.delete_server(server['id'])
+ self.client.wait_for_server_termination(server['id'])
+ resp, body = self.client.list_servers_with_detail()
+ servers = body['servers']
+ actual = [srv for srv in servers if srv['id'] == server['id']]
+ self.assertEqual('200', resp['status'])
+ self.assertEqual([], actual)
+
+ def test_get_server_details_non_existent_id(self):
+ """Return an error during get server details using non-existent id"""
+ self.assertRaises(exceptions.NotFound, self.client.get_server,
+ 'junk-123ab-45cd')
+
+ def test_get_server_details_another_tenant_server(self):
+ """Return an error when querying details of server in another tenant"""
+ server_name = rand_name('test-vm-')
+ resp, server = self.client.create_server(server_name, self.image_ref,
+ self.flavor_ref)
+ self.servers.append(server['id'])
+ self.assertRaises(exceptions.NotFound, self.alt_client.get_server,
+ server['id'])
+
+ def test_get_server_details_pass_string_uuid(self):
+ """Return an error when a string value is passed for uuid"""
+ try:
+ self.assertRaises(exceptions.NotFound, self.client.get_server,
+ 'junk-server-uuid')
+ except Exception as e:
+ self.fail("Incorrect Exception raised: %s" % e)
+
+ @unittest.skip("Until Bug 1002901 is fixed")
+ def test_get_server_details_pass_negative_uuid(self):
+ """Return an error when a negative value is passed for uuid"""
+ try:
+ self.assertRaises(exceptions.BadRequest, self.client.get_server,
+ -1)
+ except Exception as e:
+ self.fail("Incorrect Exception raised: %s" % e)
+
+ @unittest.skip("Until Bug 1002901 is fixed")
+ def test_get_server_details_pass_overlimit_length_uuid(self):
+ """Return an error when a very large value is passed for uuid"""
+ try:
+ self.assertRaises(exceptions.OverLimit, self.client.get_server,
+ 'a' * 37)
+ except Exception as e:
+ self.fail("Incorrect Exception raised: %s" % e)
diff --git a/tempest/tests/test_servers.py b/tempest/tests/test_servers.py
index b152b4c..aab7a96 100644
--- a/tempest/tests/test_servers.py
+++ b/tempest/tests/test_servers.py
@@ -12,42 +12,6 @@
cls.client = cls.servers_client
@attr(type='smoke')
- def test_create_delete_server(self):
- meta = {'hello': 'world'}
- accessIPv4 = '1.1.1.1'
- accessIPv6 = '::babe:220.12.22.2'
- name = rand_name('server')
- file_contents = 'This is a test file.'
- personality = [{'path': '/etc/test.txt',
- 'contents': base64.b64encode(file_contents)}]
- resp, server = self.client.create_server(name,
- self.image_ref,
- self.flavor_ref,
- meta=meta,
- accessIPv4=accessIPv4,
- accessIPv6=accessIPv6,
- personality=personality)
- #Check the initial response
- self.assertEqual(202, resp.status)
- self.assertTrue(server['id'] is not None)
- self.assertTrue(server['adminPass'] is not None)
-
- #Wait for the server to become active
- self.client.wait_for_server_status(server['id'], 'ACTIVE')
-
- #Verify the specified attributes are set correctly
- resp, server = self.client.get_server(server['id'])
- self.assertEqual('1.1.1.1', server['accessIPv4'])
- self.assertEqual('::babe:220.12.22.2', server['accessIPv6'])
- self.assertEqual(name, server['name'])
- self.assertEqual(self.image_ref, server['image']['id'])
- self.assertEqual(str(self.flavor_ref), server['flavor']['id'])
-
- #Delete the server
- resp, body = self.client.delete_server(server['id'])
- self.assertEqual(204, resp.status)
-
- @attr(type='smoke')
def test_create_server_with_admin_password(self):
"""
If an admin password is provided on server creation, the server's root