Adds Images API tests

* Adds new images API tests in tempest/tests/images
* Adds tempest.openstack.ServiceManager class to deal with non-Compute API
  clients
* Adds an [image] section to the configuration file and manager
  that deals with Image API stuff
* Updates the tools/conf_from_devstack script to write an image
  section to the generated tempest.conf

This is all in preparation for removing the functional integration
tests from Glance and putting them in Tempest...

Change-Id: I6caf50e5cab97794204472151acc88fcdd0fc224
diff --git a/tempest/tests/image/test_images.py b/tempest/tests/image/test_images.py
new file mode 100644
index 0000000..8567b51
--- /dev/null
+++ b/tempest/tests/image/test_images.py
@@ -0,0 +1,202 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack, LLC
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import cStringIO as StringIO
+import random
+
+import unittest2 as unittest
+
+from nose.plugins.attrib import attr
+from nose.plugins.skip import SkipTest
+
+GLANCE_INSTALLED = False
+try:
+    from glance import client
+    from glance.common import exception
+    GLANCE_INSTALLED = True
+except ImportError:
+    pass
+
+from tempest import openstack
+
+
+@unittest.skipUnless(GLANCE_INSTALLED, 'Glance not installed')
+class CreateRegisterImagesTest(unittest.TestCase):
+
+    """
+    Here we test the registration and creation of images
+    """
+
+    @classmethod
+    def setUpClass(cls):
+        cls.os = openstack.ServiceManager()
+        cls.client = cls.os.images.get_client()
+        cls.created_images = []
+
+    @classmethod
+    def tearDownClass(cls):
+        for image_id in cls.created_images:
+            cls.client.delete_image(image_id)
+
+    @attr(type='image')
+    def test_register_with_invalid_data(self):
+        """Negative tests for invalid data supplied to POST /images"""
+
+        metas = [
+            {
+                'id': '1'
+            },  # Cannot specify ID in registration
+            {
+                'container_format': 'wrong',
+            },  # Invalid container format
+            {
+                'disk_format': 'wrong',
+            },  # Invalid disk format
+        ]
+        for meta in metas:
+            try:
+                results = self.client.add_image(meta)
+            except exception.Invalid:
+                continue
+            self.fail("Did not raise Invalid for meta %s. Got results: %s" %
+                      (meta, results))
+
+    @attr(type='image')
+    def test_register_then_upload(self):
+        """Register, then upload an image"""
+        meta = {
+            'name': 'New Name',
+            'is_public': True,
+            'disk_format': 'vhd',
+            'container_format': 'bare',
+            'properties': {'prop1': 'val1'}
+        }
+        results = self.client.add_image(meta)
+        self.assertTrue('id' in results)
+        image_id = results['id']
+        self.created_images.append(image_id)
+        self.assertTrue('name' in results)
+        self.assertEqual(meta['name'], results['name'])
+        self.assertTrue('is_public' in results)
+        self.assertEqual(meta['is_public'], results['is_public'])
+        self.assertTrue('status' in results)
+        self.assertEqual('queued', results['status'])
+        self.assertTrue('properties' in results)
+        for key, val in meta['properties'].items():
+            self.assertEqual(val, results['properties'][key])
+
+        # Now try uploading an image file
+        image_file = StringIO.StringIO('*' * 1024)
+        results = self.client.update_image(image_id, image_data=image_file)
+        self.assertTrue('status' in results)
+        self.assertEqual('active', results['status'])
+        self.assertTrue('size' in results)
+        self.assertEqual(1024, results['size'])
+
+    @attr(type='image')
+    @unittest.skip('Skipping until Glance Bug 912897 is fixed')
+    def test_register_remote_image(self):
+        """Register a new remote image"""
+        meta = {
+            'name': 'New Remote Image',
+            'is_public': True,
+            'disk_format': 'raw',
+            'container_format': 'bare',
+            'location': 'http://example.com/someimage.iso'
+        }
+        results = self.client.add_image(meta)
+        self.assertTrue('id' in results)
+        image_id = results['id']
+        self.created_images.append(image_id)
+        self.assertTrue('name' in results)
+        self.assertEqual(meta['name'], results['name'])
+        self.assertTrue('is_public' in results)
+        self.assertEqual(meta['is_public'], results['is_public'])
+        self.assertTrue('status' in results)
+        self.assertEqual('active', results['status'])
+
+
+class ListImagesTest(unittest.TestCase):
+
+    """
+    Here we test the listing of image information
+    """
+
+    @classmethod
+    def setUpClass(cls):
+        raise SkipTest('Skipping until Glance Bug 912897 is fixed')
+        cls.os = openstack.ServiceManager()
+        cls.client = cls.os.images.get_client()
+        cls.created_images = []
+        cls.original_images = cls.client.get_images()
+
+        # We add a few images here to test the listing functionality of
+        # the images API
+        for x in xrange(1, 10):
+            # We make even images remote and odd images standard
+            if x % 2 == 0:
+                cls.created_images.append(cls._create_remote_image(x))
+            else:
+                cls.created_images.append(cls._create_standard_image(x))
+
+    @classmethod
+    def tearDownClass(cls):
+        for image_id in cls.created_images:
+            cls.client.delete_image(image_id)
+
+    @classmethod
+    def _create_remote_image(cls, x):
+        """
+        Create a new remote image and return the ID of the newly-registered
+        image
+        """
+        meta = {
+            'name': 'New Remote Image %s' % x,
+            'is_public': True,
+            'disk_format': 'raw',
+            'container_format': 'bare',
+            'location': 'http://example.com/someimage_%s.iso' % x
+        }
+        results = cls.client.add_image(meta)
+        image_id = results['id']
+        return image_id
+
+    @classmethod
+    def _create_standard_image(cls, x):
+        """
+        Create a new standard image and return the ID of the newly-registered
+        image. Note that the size of the new image is a random number between
+        1024 and 4096
+        """
+        meta = {
+            'name': 'New Standard Image %s' % x,
+            'is_public': True,
+            'disk_format': 'raw',
+            'container_format': 'bare'
+        }
+        image_file = StringIO.StringIO('*' * random.randint(1024, 4096))
+        results = cls.client.add_image(meta, image_file)
+        image_id = results['id']
+        return image_id
+
+    @attr(type='image')
+    def test_index_no_params(self):
+        """
+        Simple test to see all fixture images returned
+        """
+        images = self.client.get_images()
+        self.assertEqual(10, len(images) - len(cls.original_images))