Merge "Stop auto-detecting glance API versions"
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index e537c75..87bf758 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -430,6 +430,8 @@
 nova = true
 # Whether or not Heat is expected to be available
 heat = false
+# Whether or not Ceilometer is expected to be available
+ceilometer = True
 # Whether or not horizon is expected to be available
 horizon = true
 
diff --git a/tempest/api/image/v2/test_images.py b/tempest/api/image/v2/test_images.py
index eb3535f..ee6d656 100644
--- a/tempest/api/image/v2/test_images.py
+++ b/tempest/api/image/v2/test_images.py
@@ -20,6 +20,7 @@
 import random
 
 from tempest.api.image import base
+from tempest.common.utils import data_utils
 from tempest import exceptions
 from tempest.test import attr
 
@@ -42,29 +43,46 @@
                           'test', 'bare', 'wrong')
 
     @attr(type='gate')
-    def test_register_then_upload(self):
-        # Register, then upload an image
-        resp, body = self.create_image(name='New Name',
+    def test_register_upload_get_image_file(self):
+
+        """
+        Here we test these functionalities - Register image,
+        upload the image file, get image and get image file api's
+        """
+
+        image_name = data_utils.rand_name('image')
+        resp, body = self.create_image(name=image_name,
                                        container_format='bare',
                                        disk_format='raw',
                                        visibility='public')
         self.assertIn('id', body)
         image_id = body.get('id')
         self.assertIn('name', body)
-        self.assertEqual('New Name', body.get('name'))
+        self.assertEqual(image_name, body['name'])
         self.assertIn('visibility', body)
-        self.assertTrue(body.get('visibility') == 'public')
+        self.assertEqual('public', body['visibility'])
         self.assertIn('status', body)
-        self.assertEqual('queued', body.get('status'))
+        self.assertEqual('queued', body['status'])
 
         # Now try uploading an image file
-        image_file = StringIO.StringIO(('*' * 1024))
+        file_content = '*' * 1024
+        image_file = StringIO.StringIO(file_content)
         resp, body = self.client.store_image(image_id, image_file)
         self.assertEqual(resp.status, 204)
-        resp, body = self.client.get_image_metadata(image_id)
+
+        # Now try to get image details
+        resp, body = self.client.get_image(image_id)
+        self.assertEqual(200, resp.status)
+        self.assertEqual(image_id, body['id'])
+        self.assertEqual(image_name, body['name'])
         self.assertIn('size', body)
         self.assertEqual(1024, body.get('size'))
 
+        # Now try get image file
+        resp, body = self.client.get_image_file(image_id)
+        self.assertEqual(200, resp.status)
+        self.assertEqual(file_content, body)
+
 
 class ListImagesTest(base.BaseV2ImageTest):
 
@@ -107,6 +125,6 @@
             self.assertIn(image, image_list)
 
     @attr(type=['negative', 'gate'])
-    def test_get_image_meta_by_null_id(self):
+    def test_get_image_by_null_id(self):
         self.assertRaises(exceptions.NotFound,
-                          self.client.get_image_metadata, '')
+                          self.client.get_image, '')
diff --git a/tempest/api/image/v2/test_images_tags.py b/tempest/api/image/v2/test_images_tags.py
index 7e3bde4..e37e462 100644
--- a/tempest/api/image/v2/test_images_tags.py
+++ b/tempest/api/image/v2/test_images_tags.py
@@ -33,13 +33,13 @@
         # Creating image tag and verify it.
         resp, body = self.client.add_image_tag(image_id, tag)
         self.assertEqual(resp.status, 204)
-        resp, body = self.client.get_image_metadata(image_id)
+        resp, body = self.client.get_image(image_id)
         self.assertEqual(resp.status, 200)
         self.assertIn(tag, body['tags'])
 
         # Deleting image tag and verify it.
         resp = self.client.delete_image_tag(image_id, tag)
         self.assertEqual(resp.status, 204)
-        resp, body = self.client.get_image_metadata(image_id)
+        resp, body = self.client.get_image(image_id)
         self.assertEqual(resp.status, 200)
         self.assertNotIn(tag, body['tags'])
diff --git a/tempest/api/object_storage/test_healthcheck.py b/tempest/api/object_storage/test_healthcheck.py
new file mode 100644
index 0000000..798ea4f
--- /dev/null
+++ b/tempest/api/object_storage/test_healthcheck.py
@@ -0,0 +1,65 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright (C) 2013 eNovance SAS <licensing@enovance.com>
+#
+# Author: Joe H. Rahme <joe.hakim.rahme@enovance.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+from tempest.api.object_storage import base
+from tempest import clients
+from tempest.test import attr
+from tempest.test import HTTP_SUCCESS
+
+
+class HealthcheckTest(base.BaseObjectTest):
+
+    @classmethod
+    def setUpClass(cls):
+        super(HealthcheckTest, cls).setUpClass()
+
+        # creates a test user. The test user will set its base_url to the Swift
+        # endpoint and test the healthcheck feature.
+        cls.data.setup_test_user()
+
+        cls.os_test_user = clients.Manager(
+            cls.data.test_user,
+            cls.data.test_password,
+            cls.data.test_tenant)
+
+    @classmethod
+    def tearDownClass(cls):
+        cls.data.teardown_all()
+        super(HealthcheckTest, cls).tearDownClass()
+
+    def setUp(self):
+        super(HealthcheckTest, self).setUp()
+        client = self.os_test_user.account_client
+        client._set_auth()
+
+        # Turning http://.../v1/foobar into http://.../
+        client.base_url = "/".join(client.base_url.split("/")[:-2])
+
+    def tearDown(self):
+        # clear the base_url for subsequent requests
+        self.os_test_user.account_client.base_url = None
+        super(HealthcheckTest, self).tearDown()
+
+    @attr('gate')
+    def test_get_healthcheck(self):
+
+        resp, _ = self.os_test_user.account_client.get("healthcheck", {})
+
+        # The status is expected to be 200
+        self.assertIn(int(resp['status']), HTTP_SUCCESS)
diff --git a/tempest/api/volume/test_volumes_actions.py b/tempest/api/volume/test_volumes_actions.py
index 09131e2..f12d4bb 100644
--- a/tempest/api/volume/test_volumes_actions.py
+++ b/tempest/api/volume/test_volumes_actions.py
@@ -16,7 +16,7 @@
 #    under the License.
 
 from tempest.api.volume.base import BaseVolumeTest
-from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils import data_utils
 from tempest.test import attr
 from tempest.test import services
 from tempest.test import stresstest
@@ -32,8 +32,8 @@
         cls.image_client = cls.os.image_client
 
         # Create a test shared instance and volume for attach/detach tests
-        srv_name = rand_name(cls.__name__ + '-Instance-')
-        vol_name = rand_name(cls.__name__ + '-Volume-')
+        srv_name = data_utils.rand_name(cls.__name__ + '-Instance-')
+        vol_name = data_utils.rand_name(cls.__name__ + '-Volume-')
         resp, cls.server = cls.servers_client.create_server(srv_name,
                                                             cls.image_ref,
                                                             cls.flavor_ref)
@@ -102,7 +102,7 @@
         # it is shared with the other tests. After it is uploaded in Glance,
         # there is no way to delete it from Cinder, so we delete it from Glance
         # using the Glance image_client and from Cinder via tearDownClass.
-        image_name = rand_name('Image-')
+        image_name = data_utils.rand_name('Image-')
         resp, body = self.client.upload_volume(self.volume['id'],
                                                image_name,
                                                self.config.volume.disk_format)
@@ -112,6 +112,17 @@
         self.image_client.wait_for_image_status(image_id, 'active')
         self.client.wait_for_volume_status(self.volume['id'], 'available')
 
+    @attr(type='gate')
+    def test_volume_extend(self):
+        # Extend Volume Test.
+        extend_size = int(self.volume['size']) + 1
+        resp, body = self.client.extend_volume(self.volume['id'], extend_size)
+        self.assertEqual(202, resp.status)
+        self.client.wait_for_volume_status(self.volume['id'], 'available')
+        resp, volume = self.client.get_volume(self.volume['id'])
+        self.assertEqual(200, resp.status)
+        self.assertEqual(int(volume['size']), extend_size)
+
 
 class VolumesActionsTestXML(VolumesActionsTest):
     _interface = "xml"
diff --git a/tempest/api/volume/test_volumes_negative.py b/tempest/api/volume/test_volumes_negative.py
index 02adc5d..9bab9a0 100644
--- a/tempest/api/volume/test_volumes_negative.py
+++ b/tempest/api/volume/test_volumes_negative.py
@@ -177,6 +177,41 @@
                           self.client.detach_volume,
                           'xxx')
 
+    @attr(type=['negative', 'gate'])
+    def test_volume_extend_with_size_smaller_than_original_size(self):
+        # Extend volume with smaller size than original size.
+        extend_size = 0
+        self.assertRaises(exceptions.BadRequest, self.client.extend_volume,
+                          self.volume['id'], extend_size)
+
+    @attr(type=['negative', 'gate'])
+    def test_volume_extend_with_non_number_size(self):
+        # Extend volume when size is non number.
+        extend_size = 'abc'
+        self.assertRaises(exceptions.BadRequest, self.client.extend_volume,
+                          self.volume['id'], extend_size)
+
+    @attr(type=['negative', 'gate'])
+    def test_volume_extend_with_None_size(self):
+        # Extend volume with None size.
+        extend_size = None
+        self.assertRaises(exceptions.BadRequest, self.client.extend_volume,
+                          self.volume['id'], extend_size)
+
+    @attr(type=['negative', 'gate'])
+    def test_volume_extend_with_nonexistent_volume_id(self):
+        # Extend volume size when volume is nonexistent.
+        extend_size = int(self.volume['size']) + 1
+        self.assertRaises(exceptions.NotFound, self.client.extend_volume,
+                          str(uuid.uuid4()), extend_size)
+
+    @attr(type=['negative', 'gate'])
+    def test_volume_extend_without_passing_volume_id(self):
+        # Extend volume size when passing volume id is None.
+        extend_size = int(self.volume['size']) + 1
+        self.assertRaises(exceptions.NotFound, self.client.extend_volume,
+                          None, extend_size)
+
 
 class VolumesNegativeTestXML(VolumesNegativeTest):
     _interface = 'xml'
diff --git a/tempest/cli/__init__.py b/tempest/cli/__init__.py
index b082b1e..a4c8316 100644
--- a/tempest/cli/__init__.py
+++ b/tempest/cli/__init__.py
@@ -80,6 +80,12 @@
         return self.cmd_with_auth(
             'glance', action, flags, params, admin, fail_ok)
 
+    def ceilometer(self, action, flags='', params='', admin=True,
+                   fail_ok=False):
+        """Executes ceilometer command for the given action."""
+        return self.cmd_with_auth(
+            'ceilometer', action, flags, params, admin, fail_ok)
+
     def cinder(self, action, flags='', params='', admin=True, fail_ok=False):
         """Executes cinder command for the given action."""
         return self.cmd_with_auth(
diff --git a/tempest/cli/simple_read_only/test_ceilometer.py b/tempest/cli/simple_read_only/test_ceilometer.py
new file mode 100644
index 0000000..7f2864f
--- /dev/null
+++ b/tempest/cli/simple_read_only/test_ceilometer.py
@@ -0,0 +1,51 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from oslo.config import cfg
+
+import tempest.cli
+from tempest.openstack.common import log as logging
+
+CONF = cfg.CONF
+
+LOG = logging.getLogger(__name__)
+
+
+class SimpleReadOnlyCeilometerClientTest(tempest.cli.ClientTestBase):
+    """Basic, read-only tests for Ceilometer CLI client.
+
+    Checks return values and output of read-only commands.
+    These tests do not presume any content, nor do they create
+    their own. They only verify the structure of output if present.
+    """
+
+    @classmethod
+    def setUpClass(cls):
+        if (not CONF.service_available.ceilometer):
+            msg = ("Skiping all Ceilometer cli tests because it is"
+                   "not available")
+            raise cls.skipException(msg)
+        super(SimpleReadOnlyCeilometerClientTest, cls).setUpClass()
+
+    def test_ceilometer_meter_list(self):
+        self.ceilometer('meter-list')
+
+    def test_ceilometer_resource_list(self):
+        self.ceilometer('resource-list')
+
+    def test_ceilometermeter_alarm_list(self):
+        self.ceilometer('alarm-list')
diff --git a/tempest/cli/simple_read_only/test_glance.py b/tempest/cli/simple_read_only/test_glance.py
index d02c60b..a5a229c 100644
--- a/tempest/cli/simple_read_only/test_glance.py
+++ b/tempest/cli/simple_read_only/test_glance.py
@@ -18,9 +18,13 @@
 import re
 import subprocess
 
+from oslo.config import cfg
+
 import tempest.cli
 from tempest.openstack.common import log as logging
 
+CONF = cfg.CONF
+
 
 LOG = logging.getLogger(__name__)
 
@@ -45,6 +49,14 @@
             'ID', 'Name', 'Disk Format', 'Container Format',
             'Size', 'Status'])
 
+    def test_glance_member_list(self):
+        tenant_name = '--tenant-id %s' % self.identity.admin_tenant_name
+        out = self.glance('member-list',
+                          params=tenant_name)
+        endpoints = self.parser.listing(out)
+        self.assertTableStruct(endpoints,
+                               ['Image ID', 'Member ID', 'Can Share'])
+
     def test_glance_help(self):
         help_text = self.glance('help')
         lines = help_text.split('\n')
@@ -64,3 +76,14 @@
                                'member-add', 'member-create', 'member-delete',
                                'member-list'))
         self.assertFalse(wanted_commands - commands)
+
+    # Optional arguments:
+
+    def test_glance_version(self):
+        self.glance('', flags='--version')
+
+    def test_glance_debug_list(self):
+        self.glance('image-list', flags='--debug')
+
+    def test_glance_timeout(self):
+        self.glance('image-list', flags='--timeout %d' % CONF.cli.timeout)
diff --git a/tempest/config.py b/tempest/config.py
index 9123395..effa5a1 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -585,6 +585,9 @@
     cfg.BoolOpt('heat',
                 default=False,
                 help="Whether or not Heat is expected to be available"),
+    cfg.BoolOpt('ceilometer',
+                default=True,
+                help="Whether or not Ceilometer is expected to be available"),
     cfg.BoolOpt('horizon',
                 default=True,
                 help="Whether or not Horizon is expected to be available"),
diff --git a/tempest/services/image/v2/json/image_client.py b/tempest/services/image/v2/json/image_client.py
index 62b8ff6..342a09c 100644
--- a/tempest/services/image/v2/json/image_client.py
+++ b/tempest/services/image/v2/json/image_client.py
@@ -100,7 +100,7 @@
         self._validate_schema(body, type='images')
         return resp, body['images']
 
-    def get_image_metadata(self, image_id):
+    def get_image(self, image_id):
         url = 'v2/images/%s' % image_id
         resp, body = self.get(url)
         body = json.loads(body)
@@ -108,7 +108,7 @@
 
     def is_resource_deleted(self, id):
         try:
-            self.get_image_metadata(id)
+            self.get_image(id)
         except exceptions.NotFound:
             return True
         return False
diff --git a/tempest/services/volume/json/volumes_client.py b/tempest/services/volume/json/volumes_client.py
index 62a6e24..f054a2b 100644
--- a/tempest/services/volume/json/volumes_client.py
+++ b/tempest/services/volume/json/volumes_client.py
@@ -154,3 +154,13 @@
         except exceptions.NotFound:
             return True
         return False
+
+    def extend_volume(self, volume_id, extend_size):
+        """Extend a volume."""
+        post_body = {
+            'new_size': extend_size
+        }
+        post_body = json.dumps({'os-extend': post_body})
+        url = 'volumes/%s/action' % (volume_id)
+        resp, body = self.post(url, post_body, self.headers)
+        return resp, body
diff --git a/tempest/services/volume/xml/volumes_client.py b/tempest/services/volume/xml/volumes_client.py
index b59ec03..f5d8beb 100644
--- a/tempest/services/volume/xml/volumes_client.py
+++ b/tempest/services/volume/xml/volumes_client.py
@@ -227,3 +227,13 @@
         resp, body = self.post(url, str(Document(post_body)), self.headers)
         volume = xml_to_json(etree.fromstring(body))
         return resp, volume
+
+    def extend_volume(self, volume_id, extend_size):
+        """Extend a volume."""
+        post_body = Element("os-extend",
+                            new_size=extend_size)
+        url = 'volumes/%s/action' % str(volume_id)
+        resp, body = self.post(url, str(Document(post_body)), self.headers)
+        if body:
+            body = xml_to_json(etree.fromstring(body))
+        return resp, body