Merge "Allow glance tests to run on Windows"

This commit is contained in:
Zuul 2019-03-14 15:30:41 +00:00 committed by Gerrit Code Review
commit d501799a6a
19 changed files with 309 additions and 148 deletions

View File

@ -32,7 +32,6 @@ CONF = cfg.CONF
_registry_client = 'glance.registry.client' _registry_client = 'glance.registry.client'
CONF.import_opt('registry_client_protocol', _registry_client) CONF.import_opt('registry_client_protocol', _registry_client)
CONF.import_opt('registry_client_key_file', _registry_client) CONF.import_opt('registry_client_key_file', _registry_client)
CONF.import_opt('registry_client_cert_file', _registry_client)
CONF.import_opt('registry_client_ca_file', _registry_client) CONF.import_opt('registry_client_ca_file', _registry_client)
CONF.import_opt('registry_client_insecure', _registry_client) CONF.import_opt('registry_client_insecure', _registry_client)
CONF.import_opt('registry_client_timeout', _registry_client) CONF.import_opt('registry_client_timeout', _registry_client)

View File

@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import os
import eventlet import eventlet
# NOTE(jokke): As per the eventlet commit # NOTE(jokke): As per the eventlet commit
# b756447bab51046dfc6f1e0e299cc997ab343701 there's circular import happening # b756447bab51046dfc6f1e0e299cc997ab343701 there's circular import happening
@ -20,7 +22,13 @@ import eventlet
# before calling monkey_patch(). This is solved in eventlet 0.22.0 but we # before calling monkey_patch(). This is solved in eventlet 0.22.0 but we
# need to address it before that is widely used around. # need to address it before that is widely used around.
eventlet.hubs.get_hub() eventlet.hubs.get_hub()
eventlet.patcher.monkey_patch()
if os.name == 'nt':
# eventlet monkey patching the os module causes subprocess.Popen to fail
# on Windows when using pipes due to missing non-blocking IO support.
eventlet.patcher.monkey_patch(os=False)
else:
eventlet.patcher.monkey_patch()
# See http://code.google.com/p/python-nose/issues/detail?id=373 # See http://code.google.com/p/python-nose/issues/detail?id=373
# The code below enables tests to work with i18n _() blocks # The code below enables tests to work with i18n _() blocks

View File

@ -21,6 +21,7 @@ and Registry server, grabbing the logs of each, cleaning up pidfiles,
and spinning down the servers. and spinning down the servers.
""" """
import abc
import atexit import atexit
import datetime import datetime
import errno import errno
@ -28,12 +29,16 @@ import os
import platform import platform
import shutil import shutil
import signal import signal
import six
import socket import socket
import subprocess
import sys import sys
import tempfile import tempfile
import time import time
import fixtures import fixtures
from os_win import utilsfactory as os_win_utilsfactory
from oslo_config import cfg
from oslo_serialization import jsonutils from oslo_serialization import jsonutils
# NOTE(jokke): simplified transition to py3, behaves like py2 xrange # NOTE(jokke): simplified transition to py3, behaves like py2 xrange
from six.moves import range from six.moves import range
@ -48,8 +53,18 @@ from glance.tests import utils as test_utils
execute, get_unused_port = test_utils.execute, test_utils.get_unused_port execute, get_unused_port = test_utils.execute, test_utils.get_unused_port
tracecmd_osmap = {'Linux': 'strace', 'FreeBSD': 'truss'} tracecmd_osmap = {'Linux': 'strace', 'FreeBSD': 'truss'}
if os.name == 'nt':
SQLITE_CONN_TEMPLATE = 'sqlite:///%s/tests.sqlite'
else:
SQLITE_CONN_TEMPLATE = 'sqlite:////%s/tests.sqlite'
class Server(object):
CONF = cfg.CONF
CONF.import_opt('registry_host', 'glance.registry')
@six.add_metaclass(abc.ABCMeta)
class BaseServer(object):
""" """
Class used to easily manage starting and stopping Class used to easily manage starting and stopping
a server during functional test runs. a server during functional test runs.
@ -131,6 +146,78 @@ class Server(object):
return self.conf_file_name, overridden return self.conf_file_name, overridden
@abc.abstractmethod
def start(self, expect_exit=True, expected_exitcode=0, **kwargs):
pass
@abc.abstractmethod
def stop(self):
pass
def reload(self, expect_exit=True, expected_exitcode=0, **kwargs):
"""
Start and stop the service to reload
Any kwargs passed to this method will override the configuration
value in the conf file used in starting the servers.
"""
self.stop()
return self.start(expect_exit=expect_exit,
expected_exitcode=expected_exitcode, **kwargs)
def create_database(self):
"""Create database if required for this server"""
if self.needs_database:
conf_dir = os.path.join(self.test_dir, 'etc')
utils.safe_mkdirs(conf_dir)
conf_filepath = os.path.join(conf_dir, 'glance-manage.conf')
with open(conf_filepath, 'w') as conf_file:
conf_file.write('[DEFAULT]\n')
conf_file.write('sql_connection = %s' % self.sql_connection)
conf_file.flush()
glance_db_env = 'GLANCE_DB_TEST_SQLITE_FILE'
if glance_db_env in os.environ:
# use the empty db created and cached as a tempfile
# instead of spending the time creating a new one
db_location = os.environ[glance_db_env]
shutil.copyfile(db_location, "%s/tests.sqlite" % self.test_dir)
else:
cmd = ('%s -m glance.cmd.manage --config-file %s db sync' %
(sys.executable, conf_filepath))
execute(cmd, no_venv=self.no_venv, exec_env=self.exec_env,
expect_exit=True)
# copy the clean db to a temp location so that it
# can be reused for future tests
(osf, db_location) = tempfile.mkstemp()
os.close(osf)
shutil.copyfile('%s/tests.sqlite' % self.test_dir, db_location)
os.environ[glance_db_env] = db_location
# cleanup the temp file when the test suite is
# complete
def _delete_cached_db():
try:
os.remove(os.environ[glance_db_env])
except Exception:
glance_tests.logger.exception(
"Error cleaning up the file %s" %
os.environ[glance_db_env])
atexit.register(_delete_cached_db)
def dump_log(self):
if not self.log_file:
return "log_file not set for {name}".format(name=self.server_name)
elif not os.path.exists(self.log_file):
return "{log_file} for {name} did not exist".format(
log_file=self.log_file, name=self.server_name)
with open(self.log_file, 'r') as fptr:
return fptr.read().strip()
class PosixServer(BaseServer):
def start(self, expect_exit=True, expected_exitcode=0, **kwargs): def start(self, expect_exit=True, expected_exitcode=0, **kwargs):
""" """
Starts the server. Starts the server.
@ -190,61 +277,6 @@ class Server(object):
self.sock = None self.sock = None
return (rc, '', '') return (rc, '', '')
def reload(self, expect_exit=True, expected_exitcode=0, **kwargs):
"""
Start and stop the service to reload
Any kwargs passed to this method will override the configuration
value in the conf file used in starting the servers.
"""
self.stop()
return self.start(expect_exit=expect_exit,
expected_exitcode=expected_exitcode, **kwargs)
def create_database(self):
"""Create database if required for this server"""
if self.needs_database:
conf_dir = os.path.join(self.test_dir, 'etc')
utils.safe_mkdirs(conf_dir)
conf_filepath = os.path.join(conf_dir, 'glance-manage.conf')
with open(conf_filepath, 'w') as conf_file:
conf_file.write('[DEFAULT]\n')
conf_file.write('sql_connection = %s' % self.sql_connection)
conf_file.flush()
glance_db_env = 'GLANCE_DB_TEST_SQLITE_FILE'
if glance_db_env in os.environ:
# use the empty db created and cached as a tempfile
# instead of spending the time creating a new one
db_location = os.environ[glance_db_env]
os.system('cp %s %s/tests.sqlite'
% (db_location, self.test_dir))
else:
cmd = ('%s -m glance.cmd.manage --config-file %s db sync' %
(sys.executable, conf_filepath))
execute(cmd, no_venv=self.no_venv, exec_env=self.exec_env,
expect_exit=True)
# copy the clean db to a temp location so that it
# can be reused for future tests
(osf, db_location) = tempfile.mkstemp()
os.close(osf)
os.system('cp %s/tests.sqlite %s'
% (self.test_dir, db_location))
os.environ[glance_db_env] = db_location
# cleanup the temp file when the test suite is
# complete
def _delete_cached_db():
try:
os.remove(os.environ[glance_db_env])
except Exception:
glance_tests.logger.exception(
"Error cleaning up the file %s" %
os.environ[glance_db_env])
atexit.register(_delete_cached_db)
def stop(self): def stop(self):
""" """
Spin down the server. Spin down the server.
@ -257,14 +289,80 @@ class Server(object):
rc = test_utils.wait_for_fork(self.process_pid, raise_error=False) rc = test_utils.wait_for_fork(self.process_pid, raise_error=False)
return (rc, '', '') return (rc, '', '')
def dump_log(self):
if not self.log_file: class Win32Server(BaseServer):
return "log_file not set for {name}".format(name=self.server_name) def __init__(self, *args, **kwargs):
elif not os.path.exists(self.log_file): super(Win32Server, self).__init__(*args, **kwargs)
return "{log_file} for {name} did not exist".format(
log_file=self.log_file, name=self.server_name) self._processutils = os_win_utilsfactory.get_processutils()
with open(self.log_file, 'r') as fptr:
return fptr.read().strip() def start(self, expect_exit=True, expected_exitcode=0, **kwargs):
"""
Starts the server.
Any kwargs passed to this method will override the configuration
value in the conf file used in starting the servers.
"""
# Ensure the configuration file is written
self.write_conf(**kwargs)
self.create_database()
cmd = ("%(server_module)s --config-file %(conf_file_name)s"
% {"server_module": self.server_module,
"conf_file_name": self.conf_file_name})
cmd = "%s -m %s" % (sys.executable, cmd)
# Passing socket objects on Windows is a bit more cumbersome.
# We don't really have to do it.
if self.sock:
self.sock.close()
self.sock = None
self.process = subprocess.Popen(
cmd,
env=self.exec_env)
self.process_pid = self.process.pid
try:
self.job_handle = self._processutils.kill_process_on_job_close(
self.process_pid)
except Exception:
# Could not associate child process with a job, killing it.
self.process.kill()
raise
self.stop_kill = not expect_exit
if self.pid_file:
pf = open(self.pid_file, 'w')
pf.write('%d\n' % self.process_pid)
pf.close()
rc = 0
if expect_exit:
self.process.communicate()
rc = self.process.returncode
return (rc, '', '')
def stop(self):
"""
Spin down the server.
"""
if not self.process_pid:
raise Exception('Server "%s" process not running.'
% self.server_name)
if self.stop_kill:
self.process.terminate()
return (0, '', '')
if os.name == 'nt':
Server = Win32Server
else:
Server = PosixServer
class ApiServer(Server): class ApiServer(Server):
@ -305,7 +403,7 @@ class ApiServer(Server):
self.disable_path = None self.disable_path = None
self.needs_database = True self.needs_database = True
default_sql_connection = 'sqlite:////%s/tests.sqlite' % self.test_dir default_sql_connection = SQLITE_CONN_TEMPLATE % self.test_dir
self.sql_connection = os.environ.get('GLANCE_TEST_SQL_CONNECTION', self.sql_connection = os.environ.get('GLANCE_TEST_SQL_CONNECTION',
default_sql_connection) default_sql_connection)
self.data_api = kwargs.get("data_api", self.data_api = kwargs.get("data_api",
@ -488,7 +586,7 @@ class ApiServerForMultipleBackend(Server):
self.disable_path = None self.disable_path = None
self.needs_database = True self.needs_database = True
default_sql_connection = 'sqlite:////%s/tests.sqlite' % self.test_dir default_sql_connection = SQLITE_CONN_TEMPLATE % self.test_dir
self.sql_connection = os.environ.get('GLANCE_TEST_SQL_CONNECTION', self.sql_connection = os.environ.get('GLANCE_TEST_SQL_CONNECTION',
default_sql_connection) default_sql_connection)
self.data_api = kwargs.get("data_api", self.data_api = kwargs.get("data_api",
@ -646,7 +744,7 @@ class RegistryServer(Server):
self.server_module = 'glance.cmd.%s' % self.server_name self.server_module = 'glance.cmd.%s' % self.server_name
self.needs_database = True self.needs_database = True
default_sql_connection = 'sqlite:////%s/tests.sqlite' % self.test_dir default_sql_connection = SQLITE_CONN_TEMPLATE % self.test_dir
self.sql_connection = os.environ.get('GLANCE_TEST_SQL_CONNECTION', self.sql_connection = os.environ.get('GLANCE_TEST_SQL_CONNECTION',
default_sql_connection) default_sql_connection)
@ -732,7 +830,7 @@ class ScrubberDaemon(Server):
self.metadata_encryption_key = "012345678901234567890123456789ab" self.metadata_encryption_key = "012345678901234567890123456789ab"
self.lock_path = self.test_dir self.lock_path = self.test_dir
default_sql_connection = 'sqlite:////%s/tests.sqlite' % self.test_dir default_sql_connection = SQLITE_CONN_TEMPLATE % self.test_dir
self.sql_connection = os.environ.get('GLANCE_TEST_SQL_CONNECTION', self.sql_connection = os.environ.get('GLANCE_TEST_SQL_CONNECTION',
default_sql_connection) default_sql_connection)
self.policy_file = policy_file self.policy_file = policy_file
@ -790,6 +888,11 @@ class FunctionalTest(test_utils.BaseTestCase):
# False in the test SetUps that do not require Scrubber to run. # False in the test SetUps that do not require Scrubber to run.
self.include_scrubber = True self.include_scrubber = True
# The clients will try to connect to this address. Let's make sure
# we're not using the default '0.0.0.0'
self.config(bind_host='127.0.0.1',
registry_host='127.0.0.1')
self.tracecmd = tracecmd_osmap.get(platform.system()) self.tracecmd = tracecmd_osmap.get(platform.system())
conf_dir = os.path.join(self.test_dir, 'etc') conf_dir = os.path.join(self.test_dir, 'etc')

View File

@ -17,6 +17,7 @@
import copy import copy
import datetime import datetime
import time
import uuid import uuid
import mock import mock
@ -1340,6 +1341,10 @@ class DriverTests(object):
'deleted': False} 'deleted': False}
self.assertEqual(expected, member) self.assertEqual(expected, member)
# The clock may not be very accurate, for which reason we may
# get identical timestamps.
time.sleep(0.01)
member = self.db_api.image_member_update(self.context, member = self.db_api.image_member_update(self.context,
member_id, member_id,
{'status': 'accepted'}) {'status': 'accepted'})

View File

@ -346,6 +346,8 @@ class TestScrubber(functional.FunctionalTest):
def test_scrubber_restore_image_with_daemon_running(self): def test_scrubber_restore_image_with_daemon_running(self):
self.cleanup() self.cleanup()
self.scrubber_daemon.start(daemon=True) self.scrubber_daemon.start(daemon=True)
# Give the scrubber some time to start.
time.sleep(5)
exe_cmd = "%s -m glance.cmd.scrubber" % sys.executable exe_cmd = "%s -m glance.cmd.scrubber" % sys.executable
cmd = ("%s --restore fake_image_id" % exe_cmd) cmd = ("%s --restore fake_image_id" % exe_cmd)

View File

@ -45,7 +45,7 @@ class BaseCacheMiddlewareTest(object):
self.start_servers(**self.__dict__.copy()) self.start_servers(**self.__dict__.copy())
# Add an image and verify success # Add an image and verify success
path = "http://%s:%d/v2/images" % ("0.0.0.0", self.api_port) path = "http://%s:%d/v2/images" % ("127.0.0.1", self.api_port)
http = httplib2.Http() http = httplib2.Http()
headers = {'content-type': 'application/json'} headers = {'content-type': 'application/json'}
image_entity = { image_entity = {
@ -61,7 +61,7 @@ class BaseCacheMiddlewareTest(object):
data = jsonutils.loads(content) data = jsonutils.loads(content)
image_id = data['id'] image_id = data['id']
path = "http://%s:%d/v2/images/%s/file" % ("0.0.0.0", self.api_port, path = "http://%s:%d/v2/images/%s/file" % ("127.0.0.1", self.api_port,
image_id) image_id)
headers = {'content-type': 'application/octet-stream'} headers = {'content-type': 'application/octet-stream'}
image_data = "*" * FIVE_KB image_data = "*" * FIVE_KB
@ -87,7 +87,7 @@ class BaseCacheMiddlewareTest(object):
# Now, we delete the image from the server and verify that # Now, we delete the image from the server and verify that
# the image cache no longer contains the deleted image # the image cache no longer contains the deleted image
path = "http://%s:%d/v2/images/%s" % ("0.0.0.0", self.api_port, path = "http://%s:%d/v2/images/%s" % ("127.0.0.1", self.api_port,
image_id) image_id)
http = httplib2.Http() http = httplib2.Http()
response, content = http.request(path, 'DELETE') response, content = http.request(path, 'DELETE')
@ -107,7 +107,7 @@ class BaseCacheMiddlewareTest(object):
self.start_servers(**self.__dict__.copy()) self.start_servers(**self.__dict__.copy())
# Add an image and verify success # Add an image and verify success
path = "http://%s:%d/v2/images" % ("0.0.0.0", self.api_port) path = "http://%s:%d/v2/images" % ("127.0.0.1", self.api_port)
http = httplib2.Http() http = httplib2.Http()
headers = {'content-type': 'application/json'} headers = {'content-type': 'application/json'}
image_entity = { image_entity = {
@ -123,7 +123,7 @@ class BaseCacheMiddlewareTest(object):
data = jsonutils.loads(content) data = jsonutils.loads(content)
image_id = data['id'] image_id = data['id']
path = "http://%s:%d/v2/images/%s/file" % ("0.0.0.0", self.api_port, path = "http://%s:%d/v2/images/%s/file" % ("127.0.0.1", self.api_port,
image_id) image_id)
headers = {'content-type': 'application/octet-stream'} headers = {'content-type': 'application/octet-stream'}
image_data = b'ABCDEFGHIJKLMNOPQRSTUVWXYZ' image_data = b'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
@ -187,7 +187,7 @@ class BaseCacheMiddlewareTest(object):
self.start_servers(**self.__dict__.copy()) self.start_servers(**self.__dict__.copy())
# Add an image and verify success # Add an image and verify success
path = "http://%s:%d/v2/images" % ("0.0.0.0", self.api_port) path = "http://%s:%d/v2/images" % ("127.0.0.1", self.api_port)
http = httplib2.Http() http = httplib2.Http()
headers = {'content-type': 'application/json'} headers = {'content-type': 'application/json'}
image_entity = { image_entity = {
@ -203,7 +203,7 @@ class BaseCacheMiddlewareTest(object):
data = jsonutils.loads(content) data = jsonutils.loads(content)
image_id = data['id'] image_id = data['id']
path = "http://%s:%d/v2/images/%s/file" % ("0.0.0.0", self.api_port, path = "http://%s:%d/v2/images/%s/file" % ("127.0.0.1", self.api_port,
image_id) image_id)
headers = {'content-type': 'application/octet-stream'} headers = {'content-type': 'application/octet-stream'}
image_data = b'ABCDEFGHIJKLMNOPQRSTUVWXYZ' image_data = b'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
@ -283,7 +283,7 @@ class BaseCacheMiddlewareTest(object):
self.start_servers(**self.__dict__.copy()) self.start_servers(**self.__dict__.copy())
# Add an image and verify success # Add an image and verify success
path = "http://%s:%d/v2/images" % ("0.0.0.0", self.api_port) path = "http://%s:%d/v2/images" % ("127.0.0.1", self.api_port)
http = httplib2.Http() http = httplib2.Http()
headers = {'content-type': 'application/json'} headers = {'content-type': 'application/json'}
image_entity = { image_entity = {
@ -299,7 +299,7 @@ class BaseCacheMiddlewareTest(object):
data = jsonutils.loads(content) data = jsonutils.loads(content)
image_id = data['id'] image_id = data['id']
path = "http://%s:%d/v2/images/%s/file" % ("0.0.0.0", self.api_port, path = "http://%s:%d/v2/images/%s/file" % ("127.0.0.1", self.api_port,
image_id) image_id)
headers = {'content-type': 'application/octet-stream'} headers = {'content-type': 'application/octet-stream'}
image_data = "*" * FIVE_KB image_data = "*" * FIVE_KB
@ -324,7 +324,7 @@ class BaseCacheMiddlewareTest(object):
# Now, we delete the image from the server and verify that # Now, we delete the image from the server and verify that
# the image cache no longer contains the deleted image # the image cache no longer contains the deleted image
path = "http://%s:%d/v2/images/%s" % ("0.0.0.0", self.api_port, path = "http://%s:%d/v2/images/%s" % ("127.0.0.1", self.api_port,
image_id) image_id)
http = httplib2.Http() http = httplib2.Http()
response, content = http.request(path, 'DELETE') response, content = http.request(path, 'DELETE')
@ -347,7 +347,7 @@ class TestImageCacheXattr(functional.FunctionalTest,
filesystem) filesystem)
""" """
if getattr(self, 'disabled', False): if getattr(self, 'disabled', False):
return raise self.skipException('Test disabled.')
if not getattr(self, 'inited', False): if not getattr(self, 'inited', False):
try: try:
@ -356,7 +356,7 @@ class TestImageCacheXattr(functional.FunctionalTest,
self.inited = True self.inited = True
self.disabled = True self.disabled = True
self.disabled_message = ("python-xattr not installed.") self.disabled_message = ("python-xattr not installed.")
return raise self.skipException(self.disabled_message)
self.inited = True self.inited = True
self.disabled = False self.disabled = False
@ -370,7 +370,7 @@ class TestImageCacheXattr(functional.FunctionalTest,
self.inited = True self.inited = True
self.disabled = True self.disabled = True
self.disabled_message = ("filesystem does not support xattr") self.disabled_message = ("filesystem does not support xattr")
return raise self.skipException(self.disabled_message)
def tearDown(self): def tearDown(self):
super(TestImageCacheXattr, self).tearDown() super(TestImageCacheXattr, self).tearDown()

View File

@ -28,7 +28,6 @@ from glance.common import wsgi
from glance.tests import functional from glance.tests import functional
from glance.tests import utils from glance.tests import utils
eventlet.patcher.monkey_patch(socket=True) eventlet.patcher.monkey_patch(socket=True)

View File

@ -85,6 +85,12 @@ class TestLogging(functional.FunctionalTest):
""" """
Test that we notice when our log file has been rotated Test that we notice when our log file has been rotated
""" """
# Moving in-use files is not supported on Windows.
# The log handler itself may be configured to rotate files.
if os.name == 'nt':
raise self.skipException("Unsupported platform.")
self.cleanup() self.cleanup()
self.start_servers() self.start_servers()

View File

@ -32,7 +32,7 @@ class TestSqlite(functional.FunctionalTest):
self.cleanup() self.cleanup()
self.start_servers(**self.__dict__.copy()) self.start_servers(**self.__dict__.copy())
cmd = "sqlite3 tests.sqlite '.schema'" cmd = 'sqlite3 tests.sqlite ".schema"'
exitcode, out, err = execute(cmd, raise_error=True) exitcode, out, err = execute(cmd, raise_error=True)
self.assertNotIn('BIGINT', out) self.assertNotIn('BIGINT', out)

View File

@ -15,6 +15,7 @@
"""Tests for `glance.wsgi`.""" """Tests for `glance.wsgi`."""
import os
import socket import socket
import time import time
@ -52,4 +53,9 @@ class TestWSGIServer(testtools.TestCase):
# Should succeed - no timeout # Should succeed - no timeout
self.assertIn(greetings, get_request()) self.assertIn(greetings, get_request())
# Should fail - connection timed out so we get nothing from the server # Should fail - connection timed out so we get nothing from the server
self.assertFalse(get_request(delay=1.1)) if os.name == 'nt':
self.assertRaises(ConnectionAbortedError,
get_request,
delay=1.1)
else:
self.assertFalse(get_request(delay=1.1))

View File

@ -14,8 +14,6 @@
# under the License. # under the License.
import hashlib import hashlib
import os
import signal
import uuid import uuid
from oslo_serialization import jsonutils from oslo_serialization import jsonutils
@ -48,16 +46,17 @@ class TestImages(functional.FunctionalTest):
for i in range(3): for i in range(3):
ret = test_utils.start_http_server("foo_image_id%d" % i, ret = test_utils.start_http_server("foo_image_id%d" % i,
"foo_image%d" % i) "foo_image%d" % i)
setattr(self, 'http_server%d_pid' % i, ret[0]) setattr(self, 'http_server%d' % i, ret[1])
setattr(self, 'http_port%d' % i, ret[1]) setattr(self, 'http_port%d' % i, ret[2])
self.api_server.use_user_token = True self.api_server.use_user_token = True
self.api_server.send_identity_credentials = True self.api_server.send_identity_credentials = True
def tearDown(self): def tearDown(self):
for i in range(3): for i in range(3):
pid = getattr(self, 'http_server%d_pid' % i, None) httpd = getattr(self, 'http_server%d' % i, None)
if pid: if httpd:
os.kill(pid, signal.SIGKILL) httpd.shutdown()
httpd.server_close()
super(TestImages, self).tearDown() super(TestImages, self).tearDown()
@ -219,7 +218,7 @@ class TestImages(functional.FunctionalTest):
func_utils.wait_for_status(request_path=path, func_utils.wait_for_status(request_path=path,
request_headers=self._headers(), request_headers=self._headers(),
status='active', status='active',
max_sec=2, max_sec=10,
delay_sec=0.2) delay_sec=0.2)
expect_c = six.text_type(hashlib.md5(image_data).hexdigest()) expect_c = six.text_type(hashlib.md5(image_data).hexdigest())
expect_h = six.text_type(hashlib.sha512(image_data).hexdigest()) expect_h = six.text_type(hashlib.sha512(image_data).hexdigest())
@ -343,7 +342,7 @@ class TestImages(functional.FunctionalTest):
}) })
# Start http server locally # Start http server locally
pid, port = test_utils.start_standalone_http_server() thread, httpd, port = test_utils.start_standalone_http_server()
image_data_uri = 'http://localhost:%s/' % port image_data_uri = 'http://localhost:%s/' % port
data = jsonutils.dumps({'method': { data = jsonutils.dumps({'method': {
@ -373,7 +372,8 @@ class TestImages(functional.FunctionalTest):
status='active') status='active')
# kill the local http server # kill the local http server
os.kill(pid, signal.SIGKILL) httpd.shutdown()
httpd.server_close()
# Deleting image should work # Deleting image should work
path = self._url('/v2/images/%s' % image_id) path = self._url('/v2/images/%s' % image_id)
@ -1609,8 +1609,8 @@ class TestImages(functional.FunctionalTest):
path = self._url('/v2/images/%s' % image_id) path = self._url('/v2/images/%s' % image_id)
media_type = 'application/openstack-images-v2.1-json-patch' media_type = 'application/openstack-images-v2.1-json-patch'
headers = self._headers({'content-type': media_type}) headers = self._headers({'content-type': media_type})
http_server_pid, http_port = test_utils.start_http_server(image_id, thread, httpd, http_port = test_utils.start_http_server(image_id,
"image-1") "image-1")
values = [{'url': 'http://127.0.0.1:%s/image-1' % http_port, values = [{'url': 'http://127.0.0.1:%s/image-1' % http_port,
'metadata': {'idx': '0'}}] 'metadata': {'idx': '0'}}]
doc = [{'op': 'replace', doc = [{'op': 'replace',
@ -1627,7 +1627,8 @@ class TestImages(functional.FunctionalTest):
self.assertEqual(http.OK, response.status_code) self.assertEqual(http.OK, response.status_code)
# Stop http server used to update image location # Stop http server used to update image location
os.kill(http_server_pid, signal.SIGKILL) httpd.shutdown()
httpd.server_close()
# Download an image should raise HTTPServiceUnavailable # Download an image should raise HTTPServiceUnavailable
path = self._url('/v2/images/%s/file' % image_id) path = self._url('/v2/images/%s/file' % image_id)
@ -3895,14 +3896,15 @@ class TestImageLocationSelectionStrategy(functional.FunctionalTest):
for i in range(3): for i in range(3):
ret = test_utils.start_http_server("foo_image_id%d" % i, ret = test_utils.start_http_server("foo_image_id%d" % i,
"foo_image%d" % i) "foo_image%d" % i)
setattr(self, 'http_server%d_pid' % i, ret[0]) setattr(self, 'http_server%d' % i, ret[1])
setattr(self, 'http_port%d' % i, ret[1]) setattr(self, 'http_port%d' % i, ret[2])
def tearDown(self): def tearDown(self):
for i in range(3): for i in range(3):
pid = getattr(self, 'http_server%d_pid' % i, None) httpd = getattr(self, 'http_server%d' % i, None)
if pid: if httpd:
os.kill(pid, signal.SIGKILL) httpd.shutdown()
httpd.server_close()
super(TestImageLocationSelectionStrategy, self).tearDown() super(TestImageLocationSelectionStrategy, self).tearDown()
@ -4453,14 +4455,15 @@ class TestImagesMultipleBackend(functional.MultipleBackendFunctionalTest):
for i in range(3): for i in range(3):
ret = test_utils.start_http_server("foo_image_id%d" % i, ret = test_utils.start_http_server("foo_image_id%d" % i,
"foo_image%d" % i) "foo_image%d" % i)
setattr(self, 'http_server%d_pid' % i, ret[0]) setattr(self, 'http_server%d' % i, ret[1])
setattr(self, 'http_port%d' % i, ret[1]) setattr(self, 'http_port%d' % i, ret[2])
def tearDown(self): def tearDown(self):
for i in range(3): for i in range(3):
pid = getattr(self, 'http_server%d_pid' % i, None) httpd = getattr(self, 'http_server%d' % i, None)
if pid: if httpd:
os.kill(pid, signal.SIGKILL) httpd.shutdown()
httpd.server_close()
super(TestImagesMultipleBackend, self).tearDown() super(TestImagesMultipleBackend, self).tearDown()
@ -4605,7 +4608,7 @@ class TestImagesMultipleBackend(functional.MultipleBackendFunctionalTest):
func_utils.wait_for_status(request_path=path, func_utils.wait_for_status(request_path=path,
request_headers=self._headers(), request_headers=self._headers(),
status='active', status='active',
max_sec=2, max_sec=15,
delay_sec=0.2) delay_sec=0.2)
expect_c = six.text_type(hashlib.md5(image_data).hexdigest()) expect_c = six.text_type(hashlib.md5(image_data).hexdigest())
expect_h = six.text_type(hashlib.sha512(image_data).hexdigest()) expect_h = six.text_type(hashlib.sha512(image_data).hexdigest())
@ -4766,7 +4769,7 @@ class TestImagesMultipleBackend(functional.MultipleBackendFunctionalTest):
func_utils.wait_for_status(request_path=path, func_utils.wait_for_status(request_path=path,
request_headers=self._headers(), request_headers=self._headers(),
status='active', status='active',
max_sec=2, max_sec=15,
delay_sec=0.2) delay_sec=0.2)
expect_c = six.text_type(hashlib.md5(image_data).hexdigest()) expect_c = six.text_type(hashlib.md5(image_data).hexdigest())
expect_h = six.text_type(hashlib.sha512(image_data).hexdigest()) expect_h = six.text_type(hashlib.sha512(image_data).hexdigest())
@ -4909,7 +4912,7 @@ class TestImagesMultipleBackend(functional.MultipleBackendFunctionalTest):
}) })
# Start http server locally # Start http server locally
pid, port = test_utils.start_standalone_http_server() thread, httpd, port = test_utils.start_standalone_http_server()
image_data_uri = 'http://localhost:%s/' % port image_data_uri = 'http://localhost:%s/' % port
data = jsonutils.dumps({'method': { data = jsonutils.dumps({'method': {
@ -4939,7 +4942,8 @@ class TestImagesMultipleBackend(functional.MultipleBackendFunctionalTest):
status='active') status='active')
# kill the local http server # kill the local http server
os.kill(pid, signal.SIGKILL) httpd.shutdown()
httpd.server_close()
# Ensure image is created in default backend # Ensure image is created in default backend
path = self._url('/v2/images/%s' % image_id) path = self._url('/v2/images/%s' % image_id)
response = requests.get(path, headers=self._headers()) response = requests.get(path, headers=self._headers())
@ -5069,7 +5073,7 @@ class TestImagesMultipleBackend(functional.MultipleBackendFunctionalTest):
}) })
# Start http server locally # Start http server locally
pid, port = test_utils.start_standalone_http_server() thread, httpd, port = test_utils.start_standalone_http_server()
image_data_uri = 'http://localhost:%s/' % port image_data_uri = 'http://localhost:%s/' % port
data = jsonutils.dumps({'method': { data = jsonutils.dumps({'method': {
@ -5099,7 +5103,8 @@ class TestImagesMultipleBackend(functional.MultipleBackendFunctionalTest):
status='active') status='active')
# kill the local http server # kill the local http server
os.kill(pid, signal.SIGKILL) httpd.shutdown()
httpd.server_close()
# Ensure image is created in different backend # Ensure image is created in different backend
path = self._url('/v2/images/%s' % image_id) path = self._url('/v2/images/%s' % image_id)

View File

@ -15,6 +15,7 @@
import atexit import atexit
import os.path import os.path
import shutil
import tempfile import tempfile
import fixtures import fixtures
@ -163,8 +164,7 @@ class ApiTest(test_utils.BaseTestCase):
# use the empty db created and cached as a tempfile # use the empty db created and cached as a tempfile
# instead of spending the time creating a new one # instead of spending the time creating a new one
db_location = os.environ[glance_db_env] db_location = os.environ[glance_db_env]
test_utils.execute('cp %s %s/tests.sqlite' shutil.copyfile(db_location, "%s/tests.sqlite" % self.test_dir)
% (db_location, self.test_dir))
else: else:
test_utils.db_sync() test_utils.db_sync()
@ -172,8 +172,7 @@ class ApiTest(test_utils.BaseTestCase):
# can be reused for future tests # can be reused for future tests
(osf, db_location) = tempfile.mkstemp() (osf, db_location) = tempfile.mkstemp()
os.close(osf) os.close(osf)
test_utils.execute('cp %s/tests.sqlite %s' shutil.copyfile('%s/tests.sqlite' % self.test_dir, db_location)
% (self.test_dir, db_location))
os.environ[glance_db_env] = db_location os.environ[glance_db_env] = db_location
# cleanup the temp file when the test suite is # cleanup the temp file when the test suite is

View File

@ -135,8 +135,8 @@ class TestImportTask(test_utils.BaseTestCase):
self.assertFalse(os.path.exists(tmp_image_path)) self.assertFalse(os.path.exists(tmp_image_path))
self.assertTrue(os.path.exists(image_path)) self.assertTrue(os.path.exists(image_path))
self.assertEqual(1, len(list(self.image.locations))) self.assertEqual(1, len(list(self.image.locations)))
self.assertEqual("file://%s/%s" % (self.test_dir, self.assertEqual("file://%s%s%s" % (self.test_dir, os.sep,
self.image.image_id), self.image.image_id),
self.image.locations[0]['url']) self.image.locations[0]['url'])
self._assert_qemu_process_limits(tmock) self._assert_qemu_process_limits(tmock)

View File

@ -108,11 +108,9 @@ class IsolatedUnitTest(StoreClearingUnitTest):
DEFAULT_REGISTRY_PORT = 9191 DEFAULT_REGISTRY_PORT = 9191
DEFAULT_API_PORT = 9292 DEFAULT_API_PORT = 9292
if (client.port == DEFAULT_API_PORT and if client.port == DEFAULT_API_PORT:
client.host == '0.0.0.0'):
return stubs.FakeGlanceConnection return stubs.FakeGlanceConnection
elif (client.port == DEFAULT_REGISTRY_PORT and elif client.port == DEFAULT_REGISTRY_PORT:
client.host == '0.0.0.0'):
return stubs.FakeRegistryConnection(registry=self.registry) return stubs.FakeRegistryConnection(registry=self.registry)
self.patcher = mock.patch( self.patcher = mock.patch(

View File

@ -588,8 +588,11 @@ class ServerTest(test_utils.BaseTestCase):
keepalive=False, keepalive=False,
socket_timeout=900) socket_timeout=900)
def test_number_of_workers(self): def test_number_of_workers_posix(self):
"""Ensure the number of workers matches num cpus limited to 8.""" """Ensure the number of workers matches num cpus limited to 8."""
if os.name == 'nt':
raise self.skipException("Unsupported platform.")
def pid(): def pid():
i = 1 i = 1
while True: while True:

View File

@ -281,6 +281,7 @@ class ImageCacheTestCase(object):
self.assertEqual(['0', '1', '2'], self.assertEqual(['0', '1', '2'],
self.cache.get_queued_images()) self.cache.get_queued_images())
@skip_if_disabled
def test_open_for_write_good(self): def test_open_for_write_good(self):
""" """
Test to see if open_for_write works in normal case Test to see if open_for_write works in normal case
@ -300,6 +301,7 @@ class ImageCacheTestCase(object):
self.assertFalse(os.path.exists(incomplete_file_path)) self.assertFalse(os.path.exists(incomplete_file_path))
self.assertFalse(os.path.exists(invalid_file_path)) self.assertFalse(os.path.exists(invalid_file_path))
@skip_if_disabled
def test_open_for_write_with_exception(self): def test_open_for_write_with_exception(self):
""" """
Test to see if open_for_write works in a failure case for each driver Test to see if open_for_write works in a failure case for each driver
@ -324,6 +326,7 @@ class ImageCacheTestCase(object):
self.assertFalse(os.path.exists(incomplete_file_path)) self.assertFalse(os.path.exists(incomplete_file_path))
self.assertTrue(os.path.exists(invalid_file_path)) self.assertTrue(os.path.exists(invalid_file_path))
@skip_if_disabled
def test_caching_iterator(self): def test_caching_iterator(self):
""" """
Test to see if the caching iterator interacts properly with the driver Test to see if the caching iterator interacts properly with the driver
@ -351,6 +354,7 @@ class ImageCacheTestCase(object):
self.assertFalse(os.path.exists(incomplete_file_path)) self.assertFalse(os.path.exists(incomplete_file_path))
self.assertFalse(os.path.exists(invalid_file_path)) self.assertFalse(os.path.exists(invalid_file_path))
@skip_if_disabled
def test_caching_iterator_handles_backend_failure(self): def test_caching_iterator_handles_backend_failure(self):
""" """
Test that when the backend fails, caching_iter does not continue trying Test that when the backend fails, caching_iter does not continue trying
@ -374,6 +378,7 @@ class ImageCacheTestCase(object):
# make sure bad image was not cached # make sure bad image was not cached
self.assertFalse(self.cache.is_cached(image_id)) self.assertFalse(self.cache.is_cached(image_id))
@skip_if_disabled
def test_caching_iterator_falloffend(self): def test_caching_iterator_falloffend(self):
""" """
Test to see if the caching iterator interacts properly with the driver Test to see if the caching iterator interacts properly with the driver
@ -402,6 +407,7 @@ class ImageCacheTestCase(object):
self.assertFalse(os.path.exists(incomplete_file_path)) self.assertFalse(os.path.exists(incomplete_file_path))
self.assertTrue(os.path.exists(invalid_file_path)) self.assertTrue(os.path.exists(invalid_file_path))
@skip_if_disabled
def test_gate_caching_iter_good_checksum(self): def test_gate_caching_iter_good_checksum(self):
image = b"12345678990abcdefghijklmnop" image = b"12345678990abcdefghijklmnop"
image_id = 123 image_id = 123
@ -417,6 +423,7 @@ class ImageCacheTestCase(object):
# checksum is valid, fake image should be cached: # checksum is valid, fake image should be cached:
self.assertTrue(cache.is_cached(image_id)) self.assertTrue(cache.is_cached(image_id))
@skip_if_disabled
def test_gate_caching_iter_bad_checksum(self): def test_gate_caching_iter_bad_checksum(self):
image = b"12345678990abcdefghijklmnop" image = b"12345678990abcdefghijklmnop"
image_id = 123 image_id = 123

View File

@ -165,7 +165,9 @@ class TestImagesController(base.IsolatedUnitTest):
'metadata': {}, 'status': 'active'}], 'metadata': {}, 'status': 'active'}],
disk_format='raw', disk_format='raw',
container_format='bare', container_format='bare',
status='active'), status='active',
created_at=DATETIME,
updated_at=DATETIME),
_db_fixture(UUID2, owner=TENANT1, checksum=CHKSUM1, _db_fixture(UUID2, owner=TENANT1, checksum=CHKSUM1,
os_hash_algo=FAKEHASHALGO, os_hash_value=MULTIHASH2, os_hash_algo=FAKEHASHALGO, os_hash_value=MULTIHASH2,
name='2', size=512, virtual_size=2048, name='2', size=512, virtual_size=2048,
@ -175,13 +177,19 @@ class TestImagesController(base.IsolatedUnitTest):
status='active', status='active',
tags=['redhat', '64bit', 'power'], tags=['redhat', '64bit', 'power'],
properties={'hypervisor_type': 'kvm', 'foo': 'bar', properties={'hypervisor_type': 'kvm', 'foo': 'bar',
'bar': 'foo'}), 'bar': 'foo'},
created_at=DATETIME + datetime.timedelta(seconds=1),
updated_at=DATETIME + datetime.timedelta(seconds=1)),
_db_fixture(UUID3, owner=TENANT3, checksum=CHKSUM1, _db_fixture(UUID3, owner=TENANT3, checksum=CHKSUM1,
os_hash_algo=FAKEHASHALGO, os_hash_value=MULTIHASH2, os_hash_algo=FAKEHASHALGO, os_hash_value=MULTIHASH2,
name='3', size=512, virtual_size=2048, name='3', size=512, virtual_size=2048,
visibility='public', tags=['windows', '64bit', 'x86']), visibility='public', tags=['windows', '64bit', 'x86'],
created_at=DATETIME + datetime.timedelta(seconds=2),
updated_at=DATETIME + datetime.timedelta(seconds=2)),
_db_fixture(UUID4, owner=TENANT4, name='4', _db_fixture(UUID4, owner=TENANT4, name='4',
size=1024, virtual_size=3072), size=1024, virtual_size=3072,
created_at=DATETIME + datetime.timedelta(seconds=3),
updated_at=DATETIME + datetime.timedelta(seconds=3)),
] ]
[self.db.image_create(None, image) for image in self.images] [self.db.image_create(None, image) for image in self.images]
@ -4649,7 +4657,8 @@ class TestMultiImagesController(base.MultiIsolatedUnitTest):
'metadata': {}, 'status': 'active'}], 'metadata': {}, 'status': 'active'}],
disk_format='raw', disk_format='raw',
container_format='bare', container_format='bare',
status='active'), status='active',
created_at=DATETIME),
_db_fixture(UUID2, owner=TENANT1, checksum=CHKSUM1, _db_fixture(UUID2, owner=TENANT1, checksum=CHKSUM1,
name='2', size=512, virtual_size=2048, name='2', size=512, virtual_size=2048,
visibility='public', visibility='public',
@ -4658,12 +4667,15 @@ class TestMultiImagesController(base.MultiIsolatedUnitTest):
status='active', status='active',
tags=['redhat', '64bit', 'power'], tags=['redhat', '64bit', 'power'],
properties={'hypervisor_type': 'kvm', 'foo': 'bar', properties={'hypervisor_type': 'kvm', 'foo': 'bar',
'bar': 'foo'}), 'bar': 'foo'},
created_at=DATETIME + datetime.timedelta(seconds=1)),
_db_fixture(UUID3, owner=TENANT3, checksum=CHKSUM1, _db_fixture(UUID3, owner=TENANT3, checksum=CHKSUM1,
name='3', size=512, virtual_size=2048, name='3', size=512, virtual_size=2048,
visibility='public', tags=['windows', '64bit', 'x86']), visibility='public', tags=['windows', '64bit', 'x86'],
created_at=DATETIME + datetime.timedelta(seconds=2)),
_db_fixture(UUID4, owner=TENANT4, name='4', _db_fixture(UUID4, owner=TENANT4, name='4',
size=1024, virtual_size=3072), size=1024, virtual_size=3072,
created_at=DATETIME + datetime.timedelta(seconds=3)),
] ]
[self.db.image_create(None, image) for image in self.images] [self.db.image_create(None, image) for image in self.images]

View File

@ -79,7 +79,7 @@ class TestRegistryV2Client(base.IsolatedUnitTest,
created_at=uuid2_time)] created_at=uuid2_time)]
self.destroy_fixtures() self.destroy_fixtures()
self.create_fixtures() self.create_fixtures()
self.client = rclient.RegistryClient("0.0.0.0") self.client = rclient.RegistryClient("127.0.0.1")
def tearDown(self): def tearDown(self):
"""Clear the test environment""" """Clear the test environment"""

View File

@ -22,6 +22,7 @@ import shlex
import shutil import shutil
import socket import socket
import subprocess import subprocess
import threading
from alembic import command as alembic_command from alembic import command as alembic_command
import fixtures import fixtures
@ -176,7 +177,11 @@ class depends_on_exe(object):
def __call__(self, func): def __call__(self, func):
def _runner(*args, **kw): def _runner(*args, **kw):
cmd = 'which %s' % self.exe if os.name != 'nt':
cmd = 'which %s' % self.exe
else:
cmd = 'where.exe', '%s' % self.exe
exitcode, out, err = execute(cmd, raise_error=False) exitcode, out, err = execute(cmd, raise_error=False)
if exitcode != 0: if exitcode != 0:
args[0].disabled_message = 'test requires exe: %s' % self.exe args[0].disabled_message = 'test requires exe: %s' % self.exe
@ -325,7 +330,11 @@ def execute(cmd,
path_ext = [os.path.join(os.getcwd(), 'bin')] path_ext = [os.path.join(os.getcwd(), 'bin')]
# Also jack in the path cmd comes from, if it's absolute # Also jack in the path cmd comes from, if it's absolute
args = shlex.split(cmd) if os.name != 'nt':
args = shlex.split(cmd)
else:
args = cmd
executable = args[0] executable = args[0]
if os.path.isabs(executable): if os.path.isabs(executable):
path_ext.append(os.path.dirname(executable)) path_ext.append(os.path.dirname(executable))
@ -484,7 +493,7 @@ def start_http_server(image_id, image_data):
self.send_response(http.OK) self.send_response(http.OK)
self.send_header('Content-Length', str(len(fixture))) self.send_header('Content-Length', str(len(fixture)))
self.end_headers() self.end_headers()
self.wfile.write(fixture) self.wfile.write(six.b(fixture))
return return
def do_HEAD(self): def do_HEAD(self):
@ -510,11 +519,11 @@ def start_http_server(image_id, image_data):
httpd = BaseHTTPServer.HTTPServer(server_address, handler_class) httpd = BaseHTTPServer.HTTPServer(server_address, handler_class)
port = httpd.socket.getsockname()[1] port = httpd.socket.getsockname()[1]
pid = os.fork() thread = threading.Thread(target=httpd.serve_forever)
if pid == 0: thread.daemon = True
httpd.serve_forever() thread.start()
else:
return pid, port return thread, httpd, port
class RegistryAPIMixIn(object): class RegistryAPIMixIn(object):
@ -730,8 +739,8 @@ def start_standalone_http_server():
httpd = BaseHTTPServer.HTTPServer(server_address, handler_class) httpd = BaseHTTPServer.HTTPServer(server_address, handler_class)
port = httpd.socket.getsockname()[1] port = httpd.socket.getsockname()[1]
pid = os.fork() thread = threading.Thread(target=httpd.serve_forever)
if pid == 0: thread.daemon = True
httpd.serve_forever() thread.start()
else:
return pid, port return thread, httpd, port