tests.unit.test_misc.execute -> tests.utils.execute after merge
This commit is contained in:
commit
85f91b771c
@ -1,9 +1,8 @@
|
|||||||
include LICENSE run_tests.sh ChangeLog
|
include run_tests.sh
|
||||||
include README builddeb.sh
|
include README builddeb.sh
|
||||||
include MANIFEST.in pylintrc
|
include MANIFEST.in pylintrc
|
||||||
include tests/__init__.py
|
include tests/__init__.py
|
||||||
include tests/stubs.py
|
include tests/stubs.py
|
||||||
include tests/test_data.py
|
|
||||||
include tests/utils.py
|
include tests/utils.py
|
||||||
include run_tests.py
|
include run_tests.py
|
||||||
include glance/registry/db/migrate_repo/migrate.cfg
|
include glance/registry/db/migrate_repo/migrate.cfg
|
||||||
|
4
README
4
README
@ -29,10 +29,10 @@ documentation for more details).
|
|||||||
|
|
||||||
|
|
||||||
Now that Glance is installed, you can start the service. The easiest way to
|
Now that Glance is installed, you can start the service. The easiest way to
|
||||||
do that is by using the `glance-combined` utility which runs both the
|
do that is by using the `glance-control` utility which runs both the
|
||||||
`glance-api` and `glance-registry` services::
|
`glance-api` and `glance-registry` services::
|
||||||
|
|
||||||
glance-combined
|
glance-control all start
|
||||||
|
|
||||||
|
|
||||||
Once both services are running, you can now use the `glance-upload` tool to
|
Once both services are running, you can now use the `glance-upload` tool to
|
||||||
|
@ -270,7 +270,7 @@ to spell field names correctly. :)"""
|
|||||||
|
|
||||||
# Have to handle "boolean" values specially...
|
# Have to handle "boolean" values specially...
|
||||||
if 'is_public' in fields:
|
if 'is_public' in fields:
|
||||||
image_meta['is_public'] = utils.int_from_bool_as_string(
|
image_meta['is_public'] = utils.bool_from_string(
|
||||||
fields.pop('is_public'))
|
fields.pop('is_public'))
|
||||||
|
|
||||||
# Add custom attributes, which are all the arguments remaining
|
# Add custom attributes, which are all the arguments remaining
|
||||||
|
@ -35,6 +35,17 @@ import webob.dec
|
|||||||
import webob.exc
|
import webob.exc
|
||||||
|
|
||||||
|
|
||||||
|
class WritableLogger(object):
|
||||||
|
"""A thin wrapper that responds to `write` and logs."""
|
||||||
|
|
||||||
|
def __init__(self, logger, level=logging.DEBUG):
|
||||||
|
self.logger = logger
|
||||||
|
self.level = level
|
||||||
|
|
||||||
|
def write(self, msg):
|
||||||
|
self.logger.log(self.level, msg.strip("\n"))
|
||||||
|
|
||||||
|
|
||||||
def run_server(application, port):
|
def run_server(application, port):
|
||||||
"""Run a WSGI server with the given application."""
|
"""Run a WSGI server with the given application."""
|
||||||
sock = eventlet.listen(('0.0.0.0', port))
|
sock = eventlet.listen(('0.0.0.0', port))
|
||||||
@ -61,7 +72,9 @@ class Server(object):
|
|||||||
|
|
||||||
def _run(self, application, socket):
|
def _run(self, application, socket):
|
||||||
"""Start a WSGI server in a new green thread."""
|
"""Start a WSGI server in a new green thread."""
|
||||||
eventlet.wsgi.server(socket, application, custom_pool=self.pool)
|
logger = logging.getLogger('eventlet.wsgi.server')
|
||||||
|
eventlet.wsgi.server(socket, application, custom_pool=self.pool,
|
||||||
|
log=WritableLogger(logger))
|
||||||
|
|
||||||
|
|
||||||
class Middleware(object):
|
class Middleware(object):
|
||||||
|
@ -163,7 +163,9 @@ class Controller(wsgi.Controller):
|
|||||||
yield chunk
|
yield chunk
|
||||||
|
|
||||||
res = Response(app_iter=image_iterator(),
|
res = Response(app_iter=image_iterator(),
|
||||||
content_type="text/plain")
|
content_type="application/octet-stream")
|
||||||
|
# Using app_iter blanks content-length, so we set it here...
|
||||||
|
res.headers.add('Content-Length', image['size'])
|
||||||
utils.inject_image_meta_into_headers(res, image)
|
utils.inject_image_meta_into_headers(res, image)
|
||||||
return req.get_response(res)
|
return req.get_response(res)
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ class HTTPBackend(glance.store.Backend):
|
|||||||
""" An implementation of the HTTP Backend Adapter """
|
""" An implementation of the HTTP Backend Adapter """
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get(cls, parsed_uri, expected_size, conn_class=None):
|
def get(cls, parsed_uri, expected_size, options=None, conn_class=None):
|
||||||
"""Takes a parsed uri for an HTTP resource, fetches it, and yields the
|
"""Takes a parsed uri for an HTTP resource, fetches it, and yields the
|
||||||
data.
|
data.
|
||||||
"""
|
"""
|
||||||
|
1
setup.py
1
setup.py
@ -87,7 +87,6 @@ setup(
|
|||||||
],
|
],
|
||||||
scripts=['bin/glance',
|
scripts=['bin/glance',
|
||||||
'bin/glance-api',
|
'bin/glance-api',
|
||||||
'bin/glance-combined',
|
|
||||||
'bin/glance-control',
|
'bin/glance-control',
|
||||||
'bin/glance-manage',
|
'bin/glance-manage',
|
||||||
'bin/glance-registry',
|
'bin/glance-registry',
|
||||||
|
225
tests/functional/__init__.py
Normal file
225
tests/functional/__init__.py
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright 2011 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 test class for running non-stubbed tests (functional tests)
|
||||||
|
|
||||||
|
The FunctionalTest class contains helper methods for starting the API
|
||||||
|
and Registry server, grabbing the logs of each, cleaning up pidfiles,
|
||||||
|
and spinning down the servers.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import shutil
|
||||||
|
import signal
|
||||||
|
import socket
|
||||||
|
import tempfile
|
||||||
|
import time
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from tests.utils import execute, get_unused_port
|
||||||
|
|
||||||
|
|
||||||
|
class FunctionalTest(unittest.TestCase):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Base test class for any test that wants to test the actual
|
||||||
|
servers and clients and not just the stubbed out interfaces
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
|
||||||
|
self.test_id = random.randint(0, 100000)
|
||||||
|
self.test_dir = os.path.join("/", "tmp", "test.%d" % self.test_id)
|
||||||
|
|
||||||
|
self.api_port = get_unused_port()
|
||||||
|
self.api_pid_file = os.path.join(self.test_dir,
|
||||||
|
"glance-api.pid")
|
||||||
|
self.api_log_file = os.path.join(self.test_dir, "apilog")
|
||||||
|
|
||||||
|
self.registry_port = get_unused_port()
|
||||||
|
self.registry_pid_file = ("/tmp/test.%d/glance-registry.pid"
|
||||||
|
% self.test_id)
|
||||||
|
self.registry_log_file = os.path.join(self.test_dir, "registrylog")
|
||||||
|
|
||||||
|
self.image_dir = "/tmp/test.%d/images" % self.test_id
|
||||||
|
|
||||||
|
self.sql_connection = os.environ.get('GLANCE_SQL_CONNECTION',
|
||||||
|
"sqlite://")
|
||||||
|
self.pid_files = [self.api_pid_file,
|
||||||
|
self.registry_pid_file]
|
||||||
|
self.files_to_destroy = []
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.cleanup()
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
"""
|
||||||
|
Makes sure anything we created or started up in the
|
||||||
|
tests are destroyed or spun down
|
||||||
|
"""
|
||||||
|
|
||||||
|
for pid_file in self.pid_files:
|
||||||
|
if os.path.exists(pid_file):
|
||||||
|
pid = int(open(pid_file).read().strip())
|
||||||
|
try:
|
||||||
|
os.killpg(pid, signal.SIGTERM)
|
||||||
|
except:
|
||||||
|
pass # Ignore if the process group is dead
|
||||||
|
os.unlink(pid_file)
|
||||||
|
|
||||||
|
for f in self.files_to_destroy:
|
||||||
|
if os.path.exists(f):
|
||||||
|
os.unlink(f)
|
||||||
|
|
||||||
|
def start_servers(self, **kwargs):
|
||||||
|
"""
|
||||||
|
Starts the API and Registry servers (bin/glance-api and
|
||||||
|
bin/glance-registry) on unused ports and returns a tuple
|
||||||
|
of the (api_port, registry_port, conf_file_name).
|
||||||
|
|
||||||
|
Any kwargs passed to this method will override the configuration
|
||||||
|
value in the conf file used in starting the servers.
|
||||||
|
"""
|
||||||
|
self.cleanup()
|
||||||
|
|
||||||
|
conf_override = self.__dict__.copy()
|
||||||
|
if kwargs:
|
||||||
|
conf_override.update(**kwargs)
|
||||||
|
|
||||||
|
# A config file to use just for this test...we don't want
|
||||||
|
# to trample on currently-running Glance servers, now do we?
|
||||||
|
|
||||||
|
conf_file = tempfile.NamedTemporaryFile()
|
||||||
|
conf_contents = """[DEFAULT]
|
||||||
|
verbose = True
|
||||||
|
debug = True
|
||||||
|
|
||||||
|
[app:glance-api]
|
||||||
|
paste.app_factory = glance.server:app_factory
|
||||||
|
filesystem_store_datadir=%(image_dir)s
|
||||||
|
default_store = file
|
||||||
|
bind_host = 0.0.0.0
|
||||||
|
bind_port = %(api_port)s
|
||||||
|
registry_host = 0.0.0.0
|
||||||
|
registry_port = %(registry_port)s
|
||||||
|
log_file = %(api_log_file)s
|
||||||
|
|
||||||
|
[app:glance-registry]
|
||||||
|
paste.app_factory = glance.registry.server:app_factory
|
||||||
|
bind_host = 0.0.0.0
|
||||||
|
bind_port = %(registry_port)s
|
||||||
|
log_file = %(registry_log_file)s
|
||||||
|
sql_connection = %(sql_connection)s
|
||||||
|
sql_idle_timeout = 3600
|
||||||
|
""" % conf_override
|
||||||
|
conf_file.write(conf_contents)
|
||||||
|
conf_file.flush()
|
||||||
|
self.conf_file_name = conf_file.name
|
||||||
|
|
||||||
|
# Start up the API and default registry server
|
||||||
|
cmd = ("./bin/glance-control api start "
|
||||||
|
"%(conf_file_name)s --pid-file=%(api_pid_file)s"
|
||||||
|
% self.__dict__)
|
||||||
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
|
self.assertEqual(0, exitcode,
|
||||||
|
"Failed to spin up the API server. "
|
||||||
|
"Got: %s" % err)
|
||||||
|
self.assertTrue("Starting glance-api with" in out)
|
||||||
|
|
||||||
|
cmd = ("./bin/glance-control registry start "
|
||||||
|
"%(conf_file_name)s --pid-file=%(registry_pid_file)s"
|
||||||
|
% self.__dict__)
|
||||||
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
|
self.assertEqual(0, exitcode,
|
||||||
|
"Failed to spin up the Registry server. "
|
||||||
|
"Got: %s" % err)
|
||||||
|
self.assertTrue("Starting glance-registry with" in out)
|
||||||
|
|
||||||
|
self.wait_for_servers()
|
||||||
|
|
||||||
|
return self.api_port, self.registry_port, self.conf_file_name
|
||||||
|
|
||||||
|
def ping_server(self, port):
|
||||||
|
"""
|
||||||
|
Simple ping on the port. If responsive, return True, else
|
||||||
|
return False.
|
||||||
|
|
||||||
|
:note We use raw sockets, not ping here, since ping uses ICMP and
|
||||||
|
has no concept of ports...
|
||||||
|
"""
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
try:
|
||||||
|
s.connect(("127.0.0.1", port))
|
||||||
|
s.close()
|
||||||
|
return True
|
||||||
|
except socket.error, e:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def wait_for_servers(self, timeout=3):
|
||||||
|
"""
|
||||||
|
Tight loop, waiting for both API and registry server to be
|
||||||
|
available on the ports. Returns when both are pingable. There
|
||||||
|
is a timeout on waiting for the servers to come up.
|
||||||
|
|
||||||
|
:param timeout: Optional, defaults to 3
|
||||||
|
"""
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
timeout_time = now + datetime.timedelta(seconds=timeout)
|
||||||
|
while (timeout_time > now):
|
||||||
|
if self.ping_server(self.api_port) and\
|
||||||
|
self.ping_server(self.registry_port):
|
||||||
|
return
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
time.sleep(0.05)
|
||||||
|
self.assertFalse(True, "Failed to start servers.")
|
||||||
|
|
||||||
|
def stop_servers(self):
|
||||||
|
"""
|
||||||
|
Called to stop the started servers in a normal fashion. Note
|
||||||
|
that cleanup() will stop the servers using a fairly draconian
|
||||||
|
method of sending a SIGTERM signal to the servers. Here, we use
|
||||||
|
the glance-control stop method to gracefully shut the server down.
|
||||||
|
This method also asserts that the shutdown was clean, and so it
|
||||||
|
is meant to be called during a normal test case sequence.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Spin down the API and default registry server
|
||||||
|
cmd = ("./bin/glance-control api start "
|
||||||
|
"%(conf_file_name)s --pid-file=%(api_pid_file)s"
|
||||||
|
% self.__dict__)
|
||||||
|
exitcode, out, err = execute(cmd)
|
||||||
|
self.assertEqual(0, exitcode,
|
||||||
|
"Failed to spin down the API server. "
|
||||||
|
"Got: %s" % err)
|
||||||
|
cmd = ("./bin/glance-control registry start "
|
||||||
|
"%(conf_file_name)s --pid-file=%(registry_pid_file)s"
|
||||||
|
% self.__dict__)
|
||||||
|
exitcode, out, err = execute(cmd)
|
||||||
|
self.assertEqual(0, exitcode,
|
||||||
|
"Failed to spin down the Registry server. "
|
||||||
|
"Got: %s" % err)
|
||||||
|
|
||||||
|
# If all went well, then just remove the test directory.
|
||||||
|
# We only want to check the logs and stuff if something
|
||||||
|
# went wrong...
|
||||||
|
if os.path.exists(self.test_dir):
|
||||||
|
shutil.rmtree(self.test_dir)
|
152
tests/functional/test_bin_glance.py
Normal file
152
tests/functional/test_bin_glance.py
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright 2011 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.
|
||||||
|
|
||||||
|
"""Functional test case that utilizes the bin/glance CLI tool"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from tests import functional
|
||||||
|
from tests.utils import execute
|
||||||
|
|
||||||
|
|
||||||
|
class TestBinGlance(functional.FunctionalTest):
|
||||||
|
|
||||||
|
"""Functional tests for the bin/glance CLI tool"""
|
||||||
|
|
||||||
|
def test_add_list_delete_list(self):
|
||||||
|
"""
|
||||||
|
We test the following:
|
||||||
|
|
||||||
|
0. Verify no public images in index
|
||||||
|
1. Add a public image with a location attr
|
||||||
|
and no image data
|
||||||
|
2. Check that image exists in index
|
||||||
|
3. Delete the image
|
||||||
|
4. Verify no longer in index
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.cleanup()
|
||||||
|
api_port, reg_port, conf_file = self.start_servers()
|
||||||
|
|
||||||
|
# 0. Verify no public images
|
||||||
|
cmd = "bin/glance --port=%d index" % api_port
|
||||||
|
|
||||||
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
|
self.assertEqual(0, exitcode)
|
||||||
|
self.assertEqual('No public images found.', out.strip())
|
||||||
|
|
||||||
|
# 1. Add public image
|
||||||
|
cmd = "bin/glance --port=%d add is_public=True name=MyImage" % api_port
|
||||||
|
|
||||||
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
|
self.assertEqual(0, exitcode)
|
||||||
|
self.assertEqual('Added new image with ID: 1', out.strip())
|
||||||
|
|
||||||
|
# 2. Verify image added as public image
|
||||||
|
cmd = "bin/glance --port=%d index" % api_port
|
||||||
|
|
||||||
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
|
self.assertEqual(0, exitcode)
|
||||||
|
lines = out.split("\n")
|
||||||
|
first_line = lines[0]
|
||||||
|
image_data_line = lines[3]
|
||||||
|
self.assertEqual('Found 1 public images...', first_line)
|
||||||
|
self.assertTrue('MyImage' in image_data_line)
|
||||||
|
|
||||||
|
# 3. Delete the image
|
||||||
|
cmd = "bin/glance --port=%d delete 1" % api_port
|
||||||
|
|
||||||
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
|
self.assertEqual(0, exitcode)
|
||||||
|
self.assertEqual('Deleted image 1', out.strip())
|
||||||
|
|
||||||
|
# 4. Verify no public images
|
||||||
|
cmd = "bin/glance --port=%d index" % api_port
|
||||||
|
|
||||||
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
|
self.assertEqual(0, exitcode)
|
||||||
|
self.assertEqual('No public images found.', out.strip())
|
||||||
|
|
||||||
|
self.stop_servers()
|
||||||
|
|
||||||
|
def test_add_list_update_list(self):
|
||||||
|
"""
|
||||||
|
Test for LP Bug #736295
|
||||||
|
We test the following:
|
||||||
|
|
||||||
|
0. Verify no public images in index
|
||||||
|
1. Add a NON-public image
|
||||||
|
2. Check that image does not appear in index
|
||||||
|
3. Update the image to be public
|
||||||
|
4. Check that image now appears in index
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.cleanup()
|
||||||
|
api_port, reg_port, conf_file = self.start_servers()
|
||||||
|
|
||||||
|
# 0. Verify no public images
|
||||||
|
cmd = "bin/glance --port=%d index" % api_port
|
||||||
|
|
||||||
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
|
self.assertEqual(0, exitcode)
|
||||||
|
self.assertEqual('No public images found.', out.strip())
|
||||||
|
|
||||||
|
# 1. Add public image
|
||||||
|
cmd = "bin/glance --port=%d add name=MyImage" % api_port
|
||||||
|
|
||||||
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
|
self.assertEqual(0, exitcode)
|
||||||
|
self.assertEqual('Added new image with ID: 1', out.strip())
|
||||||
|
|
||||||
|
# 2. Verify image added as public image
|
||||||
|
cmd = "bin/glance --port=%d index" % api_port
|
||||||
|
|
||||||
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
|
self.assertEqual(0, exitcode)
|
||||||
|
self.assertEqual('No public images found.', out.strip())
|
||||||
|
|
||||||
|
# 3. Update the image to make it public
|
||||||
|
cmd = "bin/glance --port=%d update 1 is_public=True" % api_port
|
||||||
|
|
||||||
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
|
self.assertEqual(0, exitcode)
|
||||||
|
self.assertEqual('Updated image 1', out.strip())
|
||||||
|
|
||||||
|
# 4. Verify image 1 in list of public images
|
||||||
|
cmd = "bin/glance --port=%d index" % api_port
|
||||||
|
|
||||||
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
|
self.assertEqual(0, exitcode)
|
||||||
|
lines = out.split("\n")
|
||||||
|
first_line = lines[0]
|
||||||
|
self.assertEqual('Found 1 public images...', first_line)
|
||||||
|
|
||||||
|
image_data_line = lines[3]
|
||||||
|
self.assertTrue('MyImage' in image_data_line)
|
||||||
|
|
||||||
|
self.stop_servers()
|
302
tests/functional/test_curl_api.py
Normal file
302
tests/functional/test_curl_api.py
Normal file
@ -0,0 +1,302 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright 2011 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.
|
||||||
|
|
||||||
|
"""Functional test case that utilizes cURL against the API server"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from tests import functional
|
||||||
|
from tests.utils import execute
|
||||||
|
|
||||||
|
FIVE_KB = 5 * 1024
|
||||||
|
|
||||||
|
|
||||||
|
class TestCurlApi(functional.FunctionalTest):
|
||||||
|
|
||||||
|
"""Functional tests using straight cURL against the API server"""
|
||||||
|
|
||||||
|
def test_get_head_simple_post(self):
|
||||||
|
"""
|
||||||
|
We test the following sequential series of actions:
|
||||||
|
|
||||||
|
0. GET /images
|
||||||
|
- Verify no public images
|
||||||
|
1. GET /images/detail
|
||||||
|
- Verify no public images
|
||||||
|
2. HEAD /images/1
|
||||||
|
- Verify 404 returned
|
||||||
|
3. POST /images with public image named Image1 with a location
|
||||||
|
attribute and no custom properties
|
||||||
|
- Verify 201 returned
|
||||||
|
4. HEAD /images/1
|
||||||
|
- Verify HTTP headers have correct information we just added
|
||||||
|
5. GET /images/1
|
||||||
|
- Verify all information on image we just added is correct
|
||||||
|
6. GET /images
|
||||||
|
- Verify the image we just added is returned
|
||||||
|
7. GET /images/detail
|
||||||
|
- Verify the image we just added is returned
|
||||||
|
8. PUT /images/1 with custom properties of "distro" and "arch"
|
||||||
|
- Verify 200 returned
|
||||||
|
9. GET /images/1
|
||||||
|
- Verify updated information about image was stored
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.cleanup()
|
||||||
|
api_port, reg_port, conf_file = self.start_servers()
|
||||||
|
|
||||||
|
# 0. GET /images
|
||||||
|
# Verify no public images
|
||||||
|
cmd = "curl -g http://0.0.0.0:%d/images" % api_port
|
||||||
|
|
||||||
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
|
self.assertEqual(0, exitcode)
|
||||||
|
self.assertEqual('{"images": []}', out.strip())
|
||||||
|
|
||||||
|
# 1. GET /images/detail
|
||||||
|
# Verify no public images
|
||||||
|
cmd = "curl -g http://0.0.0.0:%d/images/detail" % api_port
|
||||||
|
|
||||||
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
|
self.assertEqual(0, exitcode)
|
||||||
|
self.assertEqual('{"images": []}', out.strip())
|
||||||
|
|
||||||
|
# 2. HEAD /images/1
|
||||||
|
# Verify 404 returned
|
||||||
|
cmd = "curl -i -X HEAD http://0.0.0.0:%d/images/1" % api_port
|
||||||
|
|
||||||
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
|
self.assertEqual(0, exitcode)
|
||||||
|
|
||||||
|
lines = out.split("\r\n")
|
||||||
|
status_line = lines[0]
|
||||||
|
|
||||||
|
self.assertEqual("HTTP/1.1 404 Not Found", status_line)
|
||||||
|
|
||||||
|
# 3. POST /images with public image named Image1
|
||||||
|
# attribute and no custom properties. Verify a 200 OK is returned
|
||||||
|
image_data = "*" * FIVE_KB
|
||||||
|
|
||||||
|
cmd = ("curl -i -X POST "
|
||||||
|
"-H 'Expect: ' " # Necessary otherwise sends 100 Continue
|
||||||
|
"-H 'Content-Type: application/octet-stream' "
|
||||||
|
"-H 'X-Image-Meta-Name: Image1' "
|
||||||
|
"-H 'X-Image-Meta-Is-Public: True' "
|
||||||
|
"--data-binary \"%s\" "
|
||||||
|
"http://0.0.0.0:%d/images") % (image_data, api_port)
|
||||||
|
|
||||||
|
exitcode, out, err = execute(cmd)
|
||||||
|
self.assertEqual(0, exitcode)
|
||||||
|
|
||||||
|
lines = out.split("\r\n")
|
||||||
|
status_line = lines[0]
|
||||||
|
|
||||||
|
self.assertEqual("HTTP/1.1 200 OK", status_line)
|
||||||
|
|
||||||
|
# 4. HEAD /images
|
||||||
|
# Verify image found now
|
||||||
|
cmd = "curl -i -X HEAD http://0.0.0.0:%d/images/1" % api_port
|
||||||
|
|
||||||
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
|
self.assertEqual(0, exitcode)
|
||||||
|
|
||||||
|
lines = out.split("\r\n")
|
||||||
|
status_line = lines[0]
|
||||||
|
|
||||||
|
self.assertEqual("HTTP/1.1 200 OK", status_line)
|
||||||
|
self.assertTrue("X-Image-Meta-Name: Image1" in out)
|
||||||
|
|
||||||
|
# 5. GET /images/1
|
||||||
|
# Verify all information on image we just added is correct
|
||||||
|
|
||||||
|
cmd = "curl -i -g http://0.0.0.0:%d/images/1" % api_port
|
||||||
|
|
||||||
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
|
self.assertEqual(0, exitcode)
|
||||||
|
|
||||||
|
lines = out.split("\r\n")
|
||||||
|
|
||||||
|
self.assertEqual("HTTP/1.1 200 OK", lines.pop(0))
|
||||||
|
|
||||||
|
# Handle the headers
|
||||||
|
image_headers = {}
|
||||||
|
std_headers = {}
|
||||||
|
other_lines = []
|
||||||
|
for line in lines:
|
||||||
|
if line.strip() == '':
|
||||||
|
continue
|
||||||
|
if line.startswith("X-Image"):
|
||||||
|
pieces = line.split(":")
|
||||||
|
key = pieces[0].strip()
|
||||||
|
value = ":".join(pieces[1:]).strip()
|
||||||
|
image_headers[key] = value
|
||||||
|
elif ':' in line:
|
||||||
|
pieces = line.split(":")
|
||||||
|
key = pieces[0].strip()
|
||||||
|
value = ":".join(pieces[1:]).strip()
|
||||||
|
std_headers[key] = value
|
||||||
|
else:
|
||||||
|
other_lines.append(line)
|
||||||
|
|
||||||
|
expected_image_headers = {
|
||||||
|
'X-Image-Meta-Id': '1',
|
||||||
|
'X-Image-Meta-Name': 'Image1',
|
||||||
|
'X-Image-Meta-Is_public': 'True',
|
||||||
|
'X-Image-Meta-Status': 'active',
|
||||||
|
'X-Image-Meta-Disk_format': '',
|
||||||
|
'X-Image-Meta-Container_format': '',
|
||||||
|
'X-Image-Meta-Size': str(FIVE_KB),
|
||||||
|
'X-Image-Meta-Location': 'file://%s/1' % self.image_dir}
|
||||||
|
|
||||||
|
expected_std_headers = {
|
||||||
|
'Content-Length': str(FIVE_KB),
|
||||||
|
'Content-Type': 'application/octet-stream'}
|
||||||
|
|
||||||
|
for expected_key, expected_value in expected_image_headers.items():
|
||||||
|
self.assertTrue(expected_key in image_headers,
|
||||||
|
"Failed to find key %s in image_headers"
|
||||||
|
% expected_key)
|
||||||
|
self.assertEqual(expected_value, image_headers[expected_key],
|
||||||
|
"For key '%s' expected header value '%s'. Got '%s'"
|
||||||
|
% (expected_key,
|
||||||
|
expected_value,
|
||||||
|
image_headers[expected_key]))
|
||||||
|
|
||||||
|
for expected_key, expected_value in expected_std_headers.items():
|
||||||
|
self.assertTrue(expected_key in std_headers,
|
||||||
|
"Failed to find key %s in std_headers"
|
||||||
|
% expected_key)
|
||||||
|
self.assertEqual(expected_value, std_headers[expected_key],
|
||||||
|
"For key '%s' expected header value '%s'. Got '%s'"
|
||||||
|
% (expected_key,
|
||||||
|
expected_value,
|
||||||
|
std_headers[expected_key]))
|
||||||
|
|
||||||
|
# Now the image data...
|
||||||
|
expected_image_data = "*" * FIVE_KB
|
||||||
|
|
||||||
|
# Should only be a single "line" left, and
|
||||||
|
# that's the image data
|
||||||
|
self.assertEqual(1, len(other_lines))
|
||||||
|
self.assertEqual(expected_image_data, other_lines[0])
|
||||||
|
|
||||||
|
# 6. GET /images
|
||||||
|
# Verify no public images
|
||||||
|
cmd = "curl -g http://0.0.0.0:%d/images" % api_port
|
||||||
|
|
||||||
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
|
self.assertEqual(0, exitcode)
|
||||||
|
|
||||||
|
expected_result = {"images": [
|
||||||
|
{"container_format": None,
|
||||||
|
"disk_format": None,
|
||||||
|
"id": 1,
|
||||||
|
"name": "Image1",
|
||||||
|
"size": 5120}]}
|
||||||
|
self.assertEqual(expected_result, json.loads(out.strip()))
|
||||||
|
|
||||||
|
# 7. GET /images/detail
|
||||||
|
# Verify image and all its metadata
|
||||||
|
cmd = "curl -g http://0.0.0.0:%d/images/detail" % api_port
|
||||||
|
|
||||||
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
|
self.assertEqual(0, exitcode)
|
||||||
|
|
||||||
|
expected_image = {
|
||||||
|
"status": "active",
|
||||||
|
"name": "Image1",
|
||||||
|
"deleted": False,
|
||||||
|
"container_format": None,
|
||||||
|
"disk_format": None,
|
||||||
|
"id": 1,
|
||||||
|
"location": "file://%s/1" % self.image_dir,
|
||||||
|
"is_public": True,
|
||||||
|
"deleted_at": None,
|
||||||
|
"properties": {},
|
||||||
|
"size": 5120}
|
||||||
|
|
||||||
|
image = json.loads(out.strip())['images'][0]
|
||||||
|
|
||||||
|
for expected_key, expected_value in expected_image.items():
|
||||||
|
self.assertTrue(expected_key in image,
|
||||||
|
"Failed to find key %s in image"
|
||||||
|
% expected_key)
|
||||||
|
self.assertEqual(expected_value, expected_image[expected_key],
|
||||||
|
"For key '%s' expected header value '%s'. Got '%s'"
|
||||||
|
% (expected_key,
|
||||||
|
expected_value,
|
||||||
|
image[expected_key]))
|
||||||
|
|
||||||
|
# 8. PUT /images/1 with custom properties of "distro" and "arch"
|
||||||
|
# Verify 200 returned
|
||||||
|
|
||||||
|
cmd = ("curl -i -X PUT "
|
||||||
|
"-H 'X-Image-Meta-Property-Distro: Ubuntu' "
|
||||||
|
"-H 'X-Image-Meta-Property-Arch: x86_64' "
|
||||||
|
"http://0.0.0.0:%d/images/1") % api_port
|
||||||
|
|
||||||
|
exitcode, out, err = execute(cmd)
|
||||||
|
self.assertEqual(0, exitcode)
|
||||||
|
|
||||||
|
lines = out.split("\r\n")
|
||||||
|
status_line = lines[0]
|
||||||
|
|
||||||
|
self.assertEqual("HTTP/1.1 200 OK", status_line)
|
||||||
|
|
||||||
|
# 9. GET /images/detail
|
||||||
|
# Verify image and all its metadata
|
||||||
|
cmd = "curl -g http://0.0.0.0:%d/images/detail" % api_port
|
||||||
|
|
||||||
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
|
self.assertEqual(0, exitcode)
|
||||||
|
|
||||||
|
expected_image = {
|
||||||
|
"status": "active",
|
||||||
|
"name": "Image1",
|
||||||
|
"deleted": False,
|
||||||
|
"container_format": None,
|
||||||
|
"disk_format": None,
|
||||||
|
"id": 1,
|
||||||
|
"location": "file://%s/1" % self.image_dir,
|
||||||
|
"is_public": True,
|
||||||
|
"deleted_at": None,
|
||||||
|
"properties": {'distro': 'Ubuntu', 'arch': 'x86_64'},
|
||||||
|
"size": 5120}
|
||||||
|
|
||||||
|
image = json.loads(out.strip())['images'][0]
|
||||||
|
|
||||||
|
for expected_key, expected_value in expected_image.items():
|
||||||
|
self.assertTrue(expected_key in image,
|
||||||
|
"Failed to find key %s in image"
|
||||||
|
% expected_key)
|
||||||
|
self.assertEqual(expected_value, image[expected_key],
|
||||||
|
"For key '%s' expected header value '%s'. Got '%s'"
|
||||||
|
% (expected_key,
|
||||||
|
expected_value,
|
||||||
|
image[expected_key]))
|
||||||
|
|
||||||
|
self.stop_servers()
|
79
tests/functional/test_logging.py
Normal file
79
tests/functional/test_logging.py
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright 2011 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 os
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from tests import functional
|
||||||
|
from tests.utils import execute
|
||||||
|
|
||||||
|
|
||||||
|
class TestLogging(functional.FunctionalTest):
|
||||||
|
|
||||||
|
"""Tests that logging can be configured correctly"""
|
||||||
|
|
||||||
|
def test_logfile(self):
|
||||||
|
"""
|
||||||
|
A test that logging can be configured properly from the
|
||||||
|
glance.conf file with the log_file option.
|
||||||
|
|
||||||
|
We start both servers daemonized with a temporary config
|
||||||
|
file that has some logging options in it.
|
||||||
|
|
||||||
|
We then use curl to issue a few requests and verify that each server's
|
||||||
|
logging statements were logged to the one log file
|
||||||
|
"""
|
||||||
|
self.cleanup()
|
||||||
|
api_port, reg_port, conf_file = self.start_servers()
|
||||||
|
|
||||||
|
cmd = "curl -X POST -H 'Content-Type: application/octet-stream' "\
|
||||||
|
"-H 'X-Image-Meta-Name: ImageName' "\
|
||||||
|
"-H 'X-Image-Meta-Disk-Format: Invalid' "\
|
||||||
|
"http://0.0.0.0:%d/images" % api_port
|
||||||
|
ignored, out, err = execute(cmd)
|
||||||
|
|
||||||
|
self.assertTrue('Invalid disk format' in out,
|
||||||
|
"Could not find 'Invalid disk format' "
|
||||||
|
"in output: %s" % out)
|
||||||
|
|
||||||
|
self.assertTrue(os.path.exists(self.api_log_file),
|
||||||
|
"API Logfile %s does not exist!"
|
||||||
|
% self.api_log_file)
|
||||||
|
self.assertTrue(os.path.exists(self.registry_log_file),
|
||||||
|
"Registry Logfile %s does not exist!"
|
||||||
|
% self.registry_log_file)
|
||||||
|
|
||||||
|
api_logfile_contents = open(self.api_log_file, 'rb').read()
|
||||||
|
registry_logfile_contents = open(self.registry_log_file, 'rb').read()
|
||||||
|
|
||||||
|
# Check that BOTH the glance API and registry server
|
||||||
|
# modules are logged to their respective logfiles.
|
||||||
|
self.assertTrue('[glance.server]'
|
||||||
|
in api_logfile_contents,
|
||||||
|
"Could not find '[glance.server]' "
|
||||||
|
"in API logfile: %s" % api_logfile_contents)
|
||||||
|
self.assertTrue('[glance.registry.server]'
|
||||||
|
in registry_logfile_contents,
|
||||||
|
"Could not find '[glance.registry.server]' "
|
||||||
|
"in Registry logfile: %s" % registry_logfile_contents)
|
||||||
|
|
||||||
|
# Test that the error we caused above is in the log
|
||||||
|
self.assertTrue('Invalid disk format' in api_logfile_contents,
|
||||||
|
"Could not find 'Invalid disk format' "
|
||||||
|
"in API logfile: %s" % api_logfile_contents)
|
||||||
|
|
||||||
|
self.stop_servers()
|
63
tests/functional/test_misc.py
Normal file
63
tests/functional/test_misc.py
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright 2011 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 unittest
|
||||||
|
|
||||||
|
from tests import functional
|
||||||
|
from tests.utils import execute
|
||||||
|
|
||||||
|
|
||||||
|
class TestMiscellaneous(functional.FunctionalTest):
|
||||||
|
|
||||||
|
"""Some random tests for various bugs and stuff"""
|
||||||
|
|
||||||
|
def test_exception_not_eaten_from_registry_to_api(self):
|
||||||
|
"""
|
||||||
|
A test for LP bug #704854 -- Exception thrown by registry
|
||||||
|
server is consumed by API server.
|
||||||
|
|
||||||
|
We start both servers daemonized.
|
||||||
|
|
||||||
|
We then use curl to try adding an image that does not
|
||||||
|
meet validation requirements on the registry server and test
|
||||||
|
that the error returned from the API server to curl is appropriate
|
||||||
|
|
||||||
|
We also fire the glance-upload tool against the API server
|
||||||
|
and verify that glance-upload doesn't eat the exception either...
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.cleanup()
|
||||||
|
api_port, reg_port, conf_file = self.start_servers()
|
||||||
|
|
||||||
|
cmd = "curl -g http://0.0.0.0:%d/images" % api_port
|
||||||
|
|
||||||
|
exitcode, out, err = execute(cmd)
|
||||||
|
|
||||||
|
self.assertEqual(0, exitcode)
|
||||||
|
self.assertEqual('{"images": []}', out.strip())
|
||||||
|
|
||||||
|
cmd = "curl -X POST -H 'Content-Type: application/octet-stream' "\
|
||||||
|
"-H 'X-Image-Meta-Name: ImageName' "\
|
||||||
|
"-H 'X-Image-Meta-Disk-Format: Invalid' "\
|
||||||
|
"http://0.0.0.0:%d/images" % api_port
|
||||||
|
ignored, out, err = execute(cmd)
|
||||||
|
|
||||||
|
self.assertTrue('Invalid disk format' in out,
|
||||||
|
"Could not find 'Invalid disk format' "
|
||||||
|
"in output: %s" % out)
|
||||||
|
|
||||||
|
self.stop_servers()
|
@ -36,7 +36,7 @@ from sqlalchemy.pool import NullPool
|
|||||||
|
|
||||||
from glance.common import exception
|
from glance.common import exception
|
||||||
import glance.registry.db.migration as migration_api
|
import glance.registry.db.migration as migration_api
|
||||||
from tests.unit.test_misc import execute
|
from tests.utils import execute
|
||||||
|
|
||||||
|
|
||||||
class TestMigrations(unittest.TestCase):
|
class TestMigrations(unittest.TestCase):
|
||||||
|
@ -1,352 +0,0 @@
|
|||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2011 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 os
|
|
||||||
import shutil
|
|
||||||
import signal
|
|
||||||
import subprocess
|
|
||||||
import tempfile
|
|
||||||
import time
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
from glance import utils
|
|
||||||
|
|
||||||
|
|
||||||
def execute(cmd):
|
|
||||||
env = os.environ.copy()
|
|
||||||
# Make sure that we use the programs in the
|
|
||||||
# current source directory's bin/ directory.
|
|
||||||
env['PATH'] = os.path.join(os.getcwd(), 'bin') + ':' + env['PATH']
|
|
||||||
process = subprocess.Popen(cmd,
|
|
||||||
shell=True,
|
|
||||||
stdin=subprocess.PIPE,
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.PIPE,
|
|
||||||
env=env)
|
|
||||||
result = process.communicate()
|
|
||||||
(out, err) = result
|
|
||||||
exitcode = process.returncode
|
|
||||||
if process.returncode != 0:
|
|
||||||
msg = "Command %(cmd)s did not succeed. Returned an exit "\
|
|
||||||
"code of %(exitcode)d."\
|
|
||||||
"\n\nSTDOUT: %(out)s"\
|
|
||||||
"\n\nSTDERR: %(err)s" % locals()
|
|
||||||
raise RuntimeError(msg)
|
|
||||||
return exitcode, out, err
|
|
||||||
|
|
||||||
|
|
||||||
class TestMiscellaneous(unittest.TestCase):
|
|
||||||
|
|
||||||
"""Some random tests for various bugs and stuff"""
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
self._cleanup_test_servers()
|
|
||||||
|
|
||||||
def _cleanup_test_servers(self):
|
|
||||||
# Clean up any leftover test servers...
|
|
||||||
pid_files = ('glance-api.pid', 'glance-registry.pid')
|
|
||||||
for pid_file in pid_files:
|
|
||||||
if os.path.exists(pid_file):
|
|
||||||
pid = int(open(pid_file).read().strip())
|
|
||||||
try:
|
|
||||||
os.killpg(pid, signal.SIGTERM)
|
|
||||||
except:
|
|
||||||
pass # Ignore if the process group is dead
|
|
||||||
os.unlink(pid_file)
|
|
||||||
|
|
||||||
def test_headers_are_unicode(self):
|
|
||||||
"""
|
|
||||||
Verifies that the headers returned by conversion code are unicode.
|
|
||||||
|
|
||||||
Headers are passed via http in non-testing mode, which automatically
|
|
||||||
converts them to unicode. Verifying that the method does the
|
|
||||||
conversion proves that we aren't passing data that works in tests
|
|
||||||
but will fail in production.
|
|
||||||
"""
|
|
||||||
fixture = {'name': 'fake public image',
|
|
||||||
'is_public': True,
|
|
||||||
'type': 'kernel',
|
|
||||||
'size': 19,
|
|
||||||
'location': "file:///tmp/glance-tests/2",
|
|
||||||
'properties': {'distro': 'Ubuntu 10.04 LTS'}}
|
|
||||||
headers = utils.image_meta_to_http_headers(fixture)
|
|
||||||
for k, v in headers.iteritems():
|
|
||||||
self.assert_(isinstance(v, unicode), "%s is not unicode" % v)
|
|
||||||
|
|
||||||
def test_data_passed_properly_through_headers(self):
|
|
||||||
"""
|
|
||||||
Verifies that data is the same after being passed through headers
|
|
||||||
"""
|
|
||||||
fixture = {'name': 'fake public image',
|
|
||||||
'is_public': True,
|
|
||||||
'deleted': False,
|
|
||||||
'type': 'kernel',
|
|
||||||
'name': None,
|
|
||||||
'size': 19,
|
|
||||||
'location': "file:///tmp/glance-tests/2",
|
|
||||||
'properties': {'distro': 'Ubuntu 10.04 LTS'}}
|
|
||||||
headers = utils.image_meta_to_http_headers(fixture)
|
|
||||||
|
|
||||||
class FakeResponse():
|
|
||||||
pass
|
|
||||||
|
|
||||||
response = FakeResponse()
|
|
||||||
response.headers = headers
|
|
||||||
result = utils.get_image_meta_from_headers(response)
|
|
||||||
for k, v in fixture.iteritems():
|
|
||||||
self.assertEqual(v, result[k])
|
|
||||||
|
|
||||||
def test_exception_not_eaten_from_registry_to_api(self):
|
|
||||||
"""
|
|
||||||
A test for LP bug #704854 -- Exception thrown by registry
|
|
||||||
server is consumed by API server.
|
|
||||||
|
|
||||||
We start both servers daemonized.
|
|
||||||
|
|
||||||
We then use curl to try adding an image that does not
|
|
||||||
meet validation requirements on the registry server and test
|
|
||||||
that the error returned from the API server to curl is appropriate
|
|
||||||
|
|
||||||
We also fire the glance-upload tool against the API server
|
|
||||||
and verify that glance-upload doesn't eat the exception either...
|
|
||||||
"""
|
|
||||||
|
|
||||||
self._cleanup_test_servers()
|
|
||||||
|
|
||||||
# Port numbers hopefully not used by anything...
|
|
||||||
api_port = 32001
|
|
||||||
reg_port = 32000
|
|
||||||
image_dir = "/tmp/test.images.%d" % api_port
|
|
||||||
sql_connection = os.environ.get('GLANCE_SQL_CONNECTION', "sqlite://")
|
|
||||||
if os.path.exists(image_dir):
|
|
||||||
shutil.rmtree(image_dir)
|
|
||||||
|
|
||||||
# A config file to use just for this test...we don't want
|
|
||||||
# to trample on currently-running Glance servers, now do we?
|
|
||||||
with tempfile.NamedTemporaryFile() as conf_file:
|
|
||||||
conf_contents = """[DEFAULT]
|
|
||||||
verbose = True
|
|
||||||
debug = True
|
|
||||||
|
|
||||||
[app:glance-api]
|
|
||||||
paste.app_factory = glance.server:app_factory
|
|
||||||
filesystem_store_datadir=%(image_dir)s
|
|
||||||
default_store = file
|
|
||||||
bind_host = 0.0.0.0
|
|
||||||
bind_port = %(api_port)s
|
|
||||||
registry_host = 0.0.0.0
|
|
||||||
registry_port = %(reg_port)s
|
|
||||||
|
|
||||||
[app:glance-registry]
|
|
||||||
paste.app_factory = glance.registry.server:app_factory
|
|
||||||
bind_host = 0.0.0.0
|
|
||||||
bind_port = %(reg_port)s
|
|
||||||
sql_connection = %(sql_connection)s
|
|
||||||
sql_idle_timeout = 3600
|
|
||||||
""" % locals()
|
|
||||||
conf_file.write(conf_contents)
|
|
||||||
conf_file.flush()
|
|
||||||
conf_file_name = conf_file.name
|
|
||||||
|
|
||||||
venv = ""
|
|
||||||
if 'VIRTUAL_ENV' in os.environ:
|
|
||||||
venv = "tools/with_venv.sh "
|
|
||||||
|
|
||||||
# Start up the API and default registry server
|
|
||||||
cmd = venv + "./bin/glance-control api start "\
|
|
||||||
"%s --pid-file=glance-api.pid" % conf_file_name
|
|
||||||
exitcode, out, err = execute(cmd)
|
|
||||||
|
|
||||||
self.assertEquals(0, exitcode)
|
|
||||||
self.assertTrue("Starting glance-api with" in out)
|
|
||||||
|
|
||||||
cmd = venv + "./bin/glance-control registry start "\
|
|
||||||
"%s --pid-file=glance-registry.pid" % conf_file_name
|
|
||||||
exitcode, out, err = execute(cmd)
|
|
||||||
|
|
||||||
self.assertEquals(0, exitcode)
|
|
||||||
self.assertTrue("Starting glance-registry with" in out)
|
|
||||||
|
|
||||||
time.sleep(2) # Gotta give some time for spin up...
|
|
||||||
|
|
||||||
cmd = "curl -g http://0.0.0.0:%d/images" % api_port
|
|
||||||
|
|
||||||
exitcode, out, err = execute(cmd)
|
|
||||||
|
|
||||||
self.assertEquals(0, exitcode)
|
|
||||||
self.assertEquals('{"images": []}', out.strip())
|
|
||||||
|
|
||||||
cmd = "curl -X POST -H 'Content-Type: application/octet-stream' "\
|
|
||||||
"-H 'X-Image-Meta-Name: ImageName' "\
|
|
||||||
"-H 'X-Image-Meta-Disk-Format: Invalid' "\
|
|
||||||
"http://0.0.0.0:%d/images" % api_port
|
|
||||||
ignored, out, err = execute(cmd)
|
|
||||||
|
|
||||||
self.assertTrue('Invalid disk format' in out,
|
|
||||||
"Could not find 'Invalid disk format' "
|
|
||||||
"in output: %s" % out)
|
|
||||||
|
|
||||||
# Spin down the API and default registry server
|
|
||||||
cmd = "./bin/glance-control api stop "\
|
|
||||||
"%s --pid-file=glance-api.pid" % conf_file_name
|
|
||||||
ignored, out, err = execute(cmd)
|
|
||||||
cmd = "./bin/glance-control registry stop "\
|
|
||||||
"%s --pid-file=glance-registry.pid" % conf_file_name
|
|
||||||
ignored, out, err = execute(cmd)
|
|
||||||
|
|
||||||
|
|
||||||
# TODO(jaypipes): Move this to separate test file once
|
|
||||||
# LP Bug#731304 moves execute() out to a common file, etc
|
|
||||||
class TestLogging(unittest.TestCase):
|
|
||||||
|
|
||||||
"""Tests that logging can be configured correctly"""
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.logfiles = []
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
self._cleanup_test_servers()
|
|
||||||
self._cleanup_log_files()
|
|
||||||
|
|
||||||
def _cleanup_test_servers(self):
|
|
||||||
# Clean up any leftover test servers...
|
|
||||||
pid_files = ('glance-api.pid', 'glance-registry.pid')
|
|
||||||
for pid_file in pid_files:
|
|
||||||
if os.path.exists(pid_file):
|
|
||||||
pid = int(open(pid_file).read().strip())
|
|
||||||
try:
|
|
||||||
os.killpg(pid, signal.SIGTERM)
|
|
||||||
except:
|
|
||||||
pass # Ignore if the process group is dead
|
|
||||||
os.unlink(pid_file)
|
|
||||||
|
|
||||||
def _cleanup_log_files(self):
|
|
||||||
for f in self.logfiles:
|
|
||||||
if os.path.exists(f):
|
|
||||||
os.unlink(f)
|
|
||||||
|
|
||||||
def test_logfile(self):
|
|
||||||
"""
|
|
||||||
A test that logging can be configured properly from the
|
|
||||||
glance.conf file with the log_file option.
|
|
||||||
|
|
||||||
We start both servers daemonized with a temporary config
|
|
||||||
file that has some logging options in it.
|
|
||||||
|
|
||||||
We then use curl to issue a few requests and verify that each server's
|
|
||||||
logging statements were logged to the one log file
|
|
||||||
"""
|
|
||||||
logfile = "/tmp/test_logfile.log"
|
|
||||||
self.logfiles.append(logfile)
|
|
||||||
|
|
||||||
if os.path.exists(logfile):
|
|
||||||
os.unlink(logfile)
|
|
||||||
|
|
||||||
self._cleanup_test_servers()
|
|
||||||
|
|
||||||
# Port numbers hopefully not used by anything...
|
|
||||||
api_port = 32001
|
|
||||||
reg_port = 32000
|
|
||||||
image_dir = "/tmp/test.images.%d" % api_port
|
|
||||||
if os.path.exists(image_dir):
|
|
||||||
shutil.rmtree(image_dir)
|
|
||||||
|
|
||||||
# A config file to use just for this test...we don't want
|
|
||||||
# to trample on currently-running Glance servers, now do we?
|
|
||||||
with tempfile.NamedTemporaryFile() as conf_file:
|
|
||||||
conf_contents = """[DEFAULT]
|
|
||||||
verbose = True
|
|
||||||
debug = True
|
|
||||||
log_file = %(logfile)s
|
|
||||||
|
|
||||||
[app:glance-api]
|
|
||||||
paste.app_factory = glance.server:app_factory
|
|
||||||
filesystem_store_datadir=%(image_dir)s
|
|
||||||
default_store = file
|
|
||||||
bind_host = 0.0.0.0
|
|
||||||
bind_port = %(api_port)s
|
|
||||||
registry_host = 0.0.0.0
|
|
||||||
registry_port = %(reg_port)s
|
|
||||||
|
|
||||||
[app:glance-registry]
|
|
||||||
paste.app_factory = glance.registry.server:app_factory
|
|
||||||
bind_host = 0.0.0.0
|
|
||||||
bind_port = %(reg_port)s
|
|
||||||
sql_connection = sqlite://
|
|
||||||
sql_idle_timeout = 3600
|
|
||||||
""" % locals()
|
|
||||||
conf_file.write(conf_contents)
|
|
||||||
conf_file.flush()
|
|
||||||
conf_file_name = conf_file.name
|
|
||||||
|
|
||||||
venv = ""
|
|
||||||
if 'VIRTUAL_ENV' in os.environ:
|
|
||||||
venv = "tools/with_venv.sh "
|
|
||||||
|
|
||||||
# Start up the API and default registry server
|
|
||||||
cmd = venv + "./bin/glance-control api start "\
|
|
||||||
"%s --pid-file=glance-api.pid" % conf_file_name
|
|
||||||
exitcode, out, err = execute(cmd)
|
|
||||||
|
|
||||||
self.assertEquals(0, exitcode)
|
|
||||||
self.assertTrue("Starting glance-api with" in out)
|
|
||||||
|
|
||||||
cmd = venv + "./bin/glance-control registry start "\
|
|
||||||
"%s --pid-file=glance-registry.pid" % conf_file_name
|
|
||||||
exitcode, out, err = execute(cmd)
|
|
||||||
|
|
||||||
self.assertEquals(0, exitcode)
|
|
||||||
self.assertTrue("Starting glance-registry with" in out)
|
|
||||||
|
|
||||||
time.sleep(2) # Gotta give some time for spin up...
|
|
||||||
|
|
||||||
cmd = "curl -X POST -H 'Content-Type: application/octet-stream' "\
|
|
||||||
"-H 'X-Image-Meta-Name: ImageName' "\
|
|
||||||
"-H 'X-Image-Meta-Disk-Format: Invalid' "\
|
|
||||||
"http://0.0.0.0:%d/images" % api_port
|
|
||||||
ignored, out, err = execute(cmd)
|
|
||||||
|
|
||||||
self.assertTrue('Invalid disk format' in out,
|
|
||||||
"Could not find 'Invalid disk format' "
|
|
||||||
"in output: %s" % out)
|
|
||||||
|
|
||||||
self.assertTrue(os.path.exists(logfile),
|
|
||||||
"Logfile %s does not exist!" % logfile)
|
|
||||||
|
|
||||||
logfile_contents = open(logfile, 'rb').read()
|
|
||||||
|
|
||||||
# Check that BOTH the glance API and registry server
|
|
||||||
# modules are logged to the file.
|
|
||||||
self.assertTrue('[glance.server]' in logfile_contents,
|
|
||||||
"Could not find '[glance.server]' "
|
|
||||||
"in logfile: %s" % logfile_contents)
|
|
||||||
self.assertTrue('[glance.registry.server]' in logfile_contents,
|
|
||||||
"Could not find '[glance.registry.server]' "
|
|
||||||
"in logfile: %s" % logfile_contents)
|
|
||||||
|
|
||||||
# Test that the error we caused above is in the log
|
|
||||||
self.assertTrue('Invalid disk format' in logfile_contents,
|
|
||||||
"Could not find 'Invalid disk format' "
|
|
||||||
"in logfile: %s" % logfile_contents)
|
|
||||||
|
|
||||||
# Spin down the API and default registry server
|
|
||||||
cmd = "./bin/glance-control api stop "\
|
|
||||||
"%s --pid-file=glance-api.pid" % conf_file_name
|
|
||||||
ignored, out, err = execute(cmd)
|
|
||||||
cmd = "./bin/glance-control registry stop "\
|
|
||||||
"%s --pid-file=glance-registry.pid" % conf_file_name
|
|
||||||
ignored, out, err = execute(cmd)
|
|
67
tests/unit/test_utils.py
Normal file
67
tests/unit/test_utils.py
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright 2011 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 unittest
|
||||||
|
|
||||||
|
from glance import utils
|
||||||
|
|
||||||
|
|
||||||
|
class TestUtils(unittest.TestCase):
|
||||||
|
|
||||||
|
"""Test routines in glance.utils"""
|
||||||
|
|
||||||
|
def test_headers_are_unicode(self):
|
||||||
|
"""
|
||||||
|
Verifies that the headers returned by conversion code are unicode.
|
||||||
|
|
||||||
|
Headers are passed via http in non-testing mode, which automatically
|
||||||
|
converts them to unicode. Verifying that the method does the
|
||||||
|
conversion proves that we aren't passing data that works in tests
|
||||||
|
but will fail in production.
|
||||||
|
"""
|
||||||
|
fixture = {'name': 'fake public image',
|
||||||
|
'is_public': True,
|
||||||
|
'type': 'kernel',
|
||||||
|
'size': 19,
|
||||||
|
'location': "file:///tmp/glance-tests/2",
|
||||||
|
'properties': {'distro': 'Ubuntu 10.04 LTS'}}
|
||||||
|
headers = utils.image_meta_to_http_headers(fixture)
|
||||||
|
for k, v in headers.iteritems():
|
||||||
|
self.assert_(isinstance(v, unicode), "%s is not unicode" % v)
|
||||||
|
|
||||||
|
def test_data_passed_properly_through_headers(self):
|
||||||
|
"""
|
||||||
|
Verifies that data is the same after being passed through headers
|
||||||
|
"""
|
||||||
|
fixture = {'name': 'fake public image',
|
||||||
|
'is_public': True,
|
||||||
|
'deleted': False,
|
||||||
|
'type': 'kernel',
|
||||||
|
'name': None,
|
||||||
|
'size': 19,
|
||||||
|
'location': "file:///tmp/glance-tests/2",
|
||||||
|
'properties': {'distro': 'Ubuntu 10.04 LTS'}}
|
||||||
|
headers = utils.image_meta_to_http_headers(fixture)
|
||||||
|
|
||||||
|
class FakeResponse():
|
||||||
|
pass
|
||||||
|
|
||||||
|
response = FakeResponse()
|
||||||
|
response.headers = headers
|
||||||
|
result = utils.get_image_meta_from_headers(response)
|
||||||
|
for k, v in fixture.iteritems():
|
||||||
|
self.assertEqual(v, result[k])
|
@ -1,6 +1,6 @@
|
|||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
# Copyright 2010 OpenStack, LLC
|
# Copyright 2010-2011 OpenStack, LLC
|
||||||
# All Rights Reserved.
|
# All Rights Reserved.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
@ -17,11 +17,41 @@
|
|||||||
|
|
||||||
"""Common utilities used in testing"""
|
"""Common utilities used in testing"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import socket
|
||||||
|
import subprocess
|
||||||
|
|
||||||
def is_swift_available():
|
|
||||||
"""Returns True if Swift/Cloudfiles is importable"""
|
def execute(cmd):
|
||||||
try:
|
env = os.environ.copy()
|
||||||
import swift
|
|
||||||
return True
|
# Make sure that we use the programs in the
|
||||||
except ImportError:
|
# current source directory's bin/ directory.
|
||||||
return False
|
env['PATH'] = os.path.join(os.getcwd(), 'bin') + ':' + env['PATH']
|
||||||
|
process = subprocess.Popen(cmd,
|
||||||
|
shell=True,
|
||||||
|
stdin=subprocess.PIPE,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
env=env)
|
||||||
|
result = process.communicate()
|
||||||
|
(out, err) = result
|
||||||
|
exitcode = process.returncode
|
||||||
|
if process.returncode != 0:
|
||||||
|
msg = "Command %(cmd)s did not succeed. Returned an exit "\
|
||||||
|
"code of %(exitcode)d."\
|
||||||
|
"\n\nSTDOUT: %(out)s"\
|
||||||
|
"\n\nSTDERR: %(err)s" % locals()
|
||||||
|
raise RuntimeError(msg)
|
||||||
|
return exitcode, out, err
|
||||||
|
|
||||||
|
|
||||||
|
def get_unused_port():
|
||||||
|
"""
|
||||||
|
Returns an unused port on localhost.
|
||||||
|
"""
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
s.bind(('localhost', 0))
|
||||||
|
addr, port = s.getsockname()
|
||||||
|
s.close()
|
||||||
|
return port
|
||||||
|
Loading…
x
Reference in New Issue
Block a user