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