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
This commit is contained in:
parent
d3bb405282
commit
5067728a02
|
@ -136,6 +136,62 @@ class EnvironmentConfig(object):
|
|||
return self.get("authentication", 'keystone')
|
||||
|
||||
|
||||
class ImagesConfig(object):
|
||||
"""
|
||||
Provides configuration information for connecting to an
|
||||
OpenStack Images service.
|
||||
"""
|
||||
|
||||
def __init__(self, conf):
|
||||
self.conf = conf
|
||||
|
||||
def get(self, item_name, default_value=None):
|
||||
try:
|
||||
return self.conf.get("image", item_name)
|
||||
except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
|
||||
return default_value
|
||||
|
||||
@property
|
||||
def host(self):
|
||||
"""Host IP for making Images API requests. Defaults to '127.0.0.1'."""
|
||||
return self.get("host", "127.0.0.1")
|
||||
|
||||
@property
|
||||
def port(self):
|
||||
"""Listen port of the Images service."""
|
||||
return int(self.get("port", "9292"))
|
||||
|
||||
@property
|
||||
def api_version(self):
|
||||
"""Version of the API"""
|
||||
return self.get("api_version", "1")
|
||||
|
||||
@property
|
||||
def username(self):
|
||||
"""Username to use for Images API requests. Defaults to 'admin'."""
|
||||
return self.get("user", "admin")
|
||||
|
||||
@property
|
||||
def password(self):
|
||||
"""Password for user"""
|
||||
return self.get("password", "")
|
||||
|
||||
@property
|
||||
def tenant(self):
|
||||
"""Tenant to use for Images API requests. Defaults to 'admin'."""
|
||||
return self.get("tenant", "admin")
|
||||
|
||||
@property
|
||||
def service_token(self):
|
||||
"""Token to use in querying the API. Default: None"""
|
||||
return self.get("service_token")
|
||||
|
||||
@property
|
||||
def auth_url(self):
|
||||
"""Optional URL to auth service. Will be discovered if None"""
|
||||
return self.get("auth_url")
|
||||
|
||||
|
||||
class TempestConfig(object):
|
||||
"""Provides OpenStack configuration information."""
|
||||
|
||||
|
@ -165,6 +221,7 @@ class TempestConfig(object):
|
|||
self._conf = self.load_config(path)
|
||||
self.nova = NovaConfig(self._conf)
|
||||
self.env = EnvironmentConfig(self._conf)
|
||||
self.images = ImagesConfig(self._conf)
|
||||
|
||||
def load_config(self, path):
|
||||
"""Read configuration from given path and return a config object."""
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import tempest.config
|
||||
from tempest.services.image import service as image_service
|
||||
from tempest.services.nova.json.images_client import ImagesClient
|
||||
from tempest.services.nova.json.flavors_client import FlavorsClient
|
||||
from tempest.services.nova.json.servers_client import ServersClient
|
||||
|
@ -74,3 +76,16 @@ class Manager(object):
|
|||
self.config.nova.username,
|
||||
self.config.nova.api_key,
|
||||
self.config.nova.auth_url)
|
||||
|
||||
|
||||
class ServiceManager(object):
|
||||
|
||||
"""
|
||||
Top-level object housing clients for OpenStack APIs
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.config = tempest.config.TempestConfig()
|
||||
self.services = {}
|
||||
self.services['image'] = image_service.Service(self.config)
|
||||
self.images = self.services['image']
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
# 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.
|
||||
|
||||
"""
|
||||
Base Service class, which acts as a descriptor for an OpenStack service
|
||||
in the test environment
|
||||
"""
|
||||
|
||||
|
||||
class Service(object):
|
||||
|
||||
def __init__(self, config):
|
||||
"""
|
||||
Initializes the service.
|
||||
|
||||
:param config: `tempest.config.Config` object
|
||||
"""
|
||||
self.config = config
|
||||
|
||||
def get_client(self):
|
||||
"""
|
||||
Returns a client object that may be used to query
|
||||
the service API.
|
||||
"""
|
||||
raise NotImplementedError
|
|
@ -0,0 +1,62 @@
|
|||
# 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.
|
||||
|
||||
"""
|
||||
Image Service class, which acts as a descriptor for the OpenStack Images
|
||||
service running in the test environment.
|
||||
"""
|
||||
|
||||
from tempest.services import Service as BaseService
|
||||
|
||||
|
||||
class Service(BaseService):
|
||||
|
||||
def __init__(self, config):
|
||||
"""
|
||||
Initializes the service.
|
||||
|
||||
:param config: `tempest.config.Config` object
|
||||
"""
|
||||
self.config = config
|
||||
|
||||
# Determine the Images API version
|
||||
self.api_version = int(config.images.api_version)
|
||||
|
||||
if self.api_version == 1:
|
||||
# We load the client class specific to the API version...
|
||||
from glance import client
|
||||
creds = {
|
||||
'username': config.images.username,
|
||||
'password': config.images.password,
|
||||
'tenant': config.images.tenant,
|
||||
'auth_url': config.images.auth_url,
|
||||
'strategy': 'keystone'
|
||||
}
|
||||
service_token = config.images.service_token
|
||||
self._client = client.Client(config.images.host,
|
||||
config.images.port,
|
||||
auth_tok=service_token)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
def get_client(self):
|
||||
"""
|
||||
Returns a client object that may be used to query
|
||||
the service API.
|
||||
"""
|
||||
assert self._client
|
||||
return self._client
|
|
@ -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))
|
|
@ -135,8 +135,7 @@ def main():
|
|||
if options.verbose:
|
||||
print "Found base image with UUID %s" % conf_settings['base_image_uuid']
|
||||
|
||||
tempest_conf = """
|
||||
[nova]
|
||||
tempest_conf = """[nova]
|
||||
host=%(service_host)s
|
||||
port=%(service_port)s
|
||||
apiVer=%(identity_api_version)s
|
||||
|
@ -148,6 +147,16 @@ ssh_timeout=300
|
|||
build_interval=10
|
||||
build_timeout=600
|
||||
|
||||
[image]
|
||||
host = %(service_host)s
|
||||
port = 9292
|
||||
username = %(user)s
|
||||
password = %(password)s
|
||||
tenant = %(user)s
|
||||
auth_url = http://127.0.0.1:5000/v2.0/tokens/
|
||||
strategy = keystone
|
||||
service_token = servicetoken
|
||||
|
||||
[environment]
|
||||
image_ref=%(base_image_uuid)s
|
||||
image_ref_alt=4
|
||||
|
|
Loading…
Reference in New Issue