Remove Windows OS support

Windows OS support was deprecated in 2024.1 cycle because Winstackers
project was retired[1]. Remove the support now to get rid of os-win
which was also abandoned.

[1] a8bed388f2

Change-Id: I737279c93a6231ebf2e8c87810040ce48622f4fc
This commit is contained in:
Takashi Kajinami 2024-09-22 21:35:34 +09:00
parent 704b24fd67
commit acab9351a1
18 changed files with 22 additions and 335 deletions

View File

@ -26,10 +26,6 @@ of its features. Some examples include the ``qemu-img`` utility used by the
tasks feature, ``pydev`` to debug using popular IDEs, ``python-xattr`` for tasks feature, ``pydev`` to debug using popular IDEs, ``python-xattr`` for
Image Cache using "xattr" driver. Image Cache using "xattr" driver.
Additionally, some libraries like ``xattr`` are not compatible when
using Glance on Windows (see :ref:`the documentation on config options
affecting the Image Cache <configuring>`).
Guideline to include your requirement in the requirements.txt file Guideline to include your requirement in the requirements.txt file
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -1756,8 +1756,7 @@ One main configuration file option affects the image cache.
and requires that the filesystem containing ``image_cache_dir`` have and requires that the filesystem containing ``image_cache_dir`` have
access times tracked for all files (in other words, the noatime option access times tracked for all files (in other words, the noatime option
CANNOT be set for that filesystem). In addition, ``user_xattr`` must be CANNOT be set for that filesystem). In addition, ``user_xattr`` must be
set on the filesystem's description line in fstab. Because of these set on the filesystem's description line in fstab.
requirements, the ``xattr`` cache driver is not available on Windows.
``image_cache_sqlite_db=DB_FILE`` ``image_cache_sqlite_db=DB_FILE``
Optional. Optional.

View File

@ -25,13 +25,7 @@ import os
import sys import sys
import eventlet import eventlet
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()
# Monkey patch the original current_thread to use the up-to-date _active # Monkey patch the original current_thread to use the up-to-date _active
# global variable. See https://bugs.launchpad.net/bugs/1863021 and # global variable. See https://bugs.launchpad.net/bugs/1863021 and
@ -66,7 +60,6 @@ from glance import version
CONF = cfg.CONF CONF = cfg.CONF
CONF.import_group("profiler", "glance.common.wsgi") CONF.import_group("profiler", "glance.common.wsgi")
logging.register_options(CONF) logging.register_options(CONF)
wsgi.register_cli_opts()
# NOTE(rosmaita): Any new exceptions added should preserve the current # NOTE(rosmaita): Any new exceptions added should preserve the current
# error codes for backward compatibility. The value 99 is returned # error codes for backward compatibility. The value 99 is returned

View File

@ -23,13 +23,7 @@ import os
import sys import sys
import eventlet import eventlet
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()
# Monkey patch the original current_thread to use the up-to-date _active # Monkey patch the original current_thread to use the up-to-date _active
# global variable. See https://bugs.launchpad.net/bugs/1863021 and # global variable. See https://bugs.launchpad.net/bugs/1863021 and
@ -49,7 +43,6 @@ if os.path.exists(os.path.join(possible_topdir, 'glance', '__init__.py')):
sys.path.insert(0, possible_topdir) sys.path.insert(0, possible_topdir)
import glance_store import glance_store
from os_win import utilsfactory as os_win_utilsfactory
from oslo_config import cfg from oslo_config import cfg
from oslo_log import log as logging from oslo_log import log as logging
@ -64,18 +57,7 @@ CONF.set_default(name='use_stderr', default=True)
def main(): def main():
# Used on Window, ensuring that a single scrubber can run at a time.
mutex = None
mutex_acquired = False
try: try:
if os.name == 'nt':
# We can't rely on process names on Windows as there may be
# wrappers with the same name.
mutex = os_win_utilsfactory.get_mutex(
name='Global\\glance-scrubber')
mutex_acquired = mutex.acquire(timeout_ms=0)
CONF.register_cli_opts(scrubber.scrubber_cmd_cli_opts) CONF.register_cli_opts(scrubber.scrubber_cmd_cli_opts)
CONF.register_opts(scrubber.scrubber_cmd_opts) CONF.register_opts(scrubber.scrubber_cmd_opts)
@ -99,12 +81,7 @@ def main():
app = scrubber.Scrubber(glance_store) app = scrubber.Scrubber(glance_store)
if CONF.restore: if CONF.restore:
if os.name == 'nt': if scrubber_already_running():
scrubber_already_running = not mutex_acquired
else:
scrubber_already_running = scrubber_already_running_posix()
if scrubber_already_running:
already_running_msg = ( already_running_msg = (
"ERROR: glance-scrubber is already running. " "ERROR: glance-scrubber is already running. "
"Please ensure that the daemon is stopped.") "Please ensure that the daemon is stopped.")
@ -121,12 +98,9 @@ def main():
sys.exit("ERROR: %s" % e) sys.exit("ERROR: %s" % e)
except RuntimeError as e: except RuntimeError as e:
sys.exit("ERROR: %s" % e) sys.exit("ERROR: %s" % e)
finally:
if mutex and mutex_acquired:
mutex.release()
def scrubber_already_running_posix(): def scrubber_already_running():
# Try to check the glance-scrubber is running or not. # Try to check the glance-scrubber is running or not.
# 1. Try to find the pid file if scrubber is controlled by # 1. Try to find the pid file if scrubber is controlled by
# glance-control # glance-control

View File

@ -24,10 +24,7 @@ import abc
import errno import errno
import functools import functools
import os import os
import re
import signal import signal
import struct
import subprocess
import sys import sys
import time import time
@ -35,7 +32,6 @@ from eventlet.green import socket
import eventlet.greenio import eventlet.greenio
import eventlet.wsgi import eventlet.wsgi
import glance_store import glance_store
from os_win import utilsfactory as os_win_utilsfactory
from oslo_concurrency import processutils from oslo_concurrency import processutils
from oslo_config import cfg from oslo_config import cfg
from oslo_log import log as logging from oslo_log import log as logging
@ -252,13 +248,6 @@ store_opts = [
'using comma.')), 'using comma.')),
] ]
cli_opts = [
cfg.StrOpt('pipe-handle',
help='This argument is used internally on Windows. Glance '
'passes a pipe handle to child processes, which is then '
'used for inter-process communication.'),
]
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -286,17 +275,9 @@ RESERVED_STORES = {
} }
def register_cli_opts():
CONF.register_cli_opts(cli_opts)
def get_num_workers(): def get_num_workers():
"""Return the configured number of workers.""" """Return the configured number of workers."""
# Windows only: we're already running on the worker side.
if os.name == 'nt' and getattr(CONF, 'pipe_handle', None):
return 0
if CONF.workers is None: if CONF.workers is None:
# None implies the number of CPUs limited to 8 # None implies the number of CPUs limited to 8
# See Launchpad bug #1748916 and the config help text # See Launchpad bug #1748916 and the config help text
@ -740,125 +721,7 @@ class PosixServer(BaseServer):
self.start_wsgi() self.start_wsgi()
class Win32ProcessLauncher(object): Server = PosixServer
def __init__(self):
self._processutils = os_win_utilsfactory.get_processutils()
self._workers = []
self._worker_job_handles = []
def add_process(self, cmd):
LOG.info("Starting subprocess: %s", cmd)
worker = subprocess.Popen(cmd, close_fds=False)
try:
job_handle = self._processutils.kill_process_on_job_close(
worker.pid)
except Exception:
LOG.exception("Could not associate child process "
"with a job, killing it.")
worker.kill()
raise
self._worker_job_handles.append(job_handle)
self._workers.append(worker)
return worker
def wait(self):
pids = [worker.pid for worker in self._workers]
if pids:
self._processutils.wait_for_multiple_processes(pids,
wait_all=True)
# By sleeping here, we allow signal handlers to be executed.
time.sleep(0)
class Win32Server(BaseServer):
_py_script_re = re.compile(r'.*\.py\w?$')
_sock = None
def __init__(self, *args, **kwargs):
LOG.warning("Support for Glance on Windows operating systems is"
"deprecated.")
super(Win32Server, self).__init__(*args, **kwargs)
self._launcher = Win32ProcessLauncher()
self._ioutils = os_win_utilsfactory.get_ioutils()
def run_child(self):
# We're passing copies of the socket through pipes.
rfd, wfd = self._ioutils.create_pipe(inherit_handle=True)
cmd = sys.argv + ['--pipe-handle=%s' % int(rfd)]
# Recent setuptools versions will trim '-script.py' and '.exe'
# extensions from sys.argv[0].
if self._py_script_re.match(sys.argv[0]):
cmd = [sys.executable] + cmd
worker = self._launcher.add_process(cmd)
self._ioutils.close_handle(rfd)
share_sock_buff = self._sock.share(worker.pid)
self._ioutils.write_file(
wfd,
struct.pack('<I', len(share_sock_buff)),
4)
self._ioutils.write_file(
wfd, share_sock_buff, len(share_sock_buff))
self.children.add(worker.pid)
def kill_children(self, *args):
# We're using job objects, the children will exit along with the
# main process.
exit(0)
def wait_on_children(self):
self._launcher.wait()
def _get_sock_from_parent(self):
# This is supposed to be called exactly once in the child process.
# We're passing a copy of the socket through a pipe.
pipe_handle = int(getattr(CONF, 'pipe_handle', 0))
if not pipe_handle:
err_msg = _("Did not receive a pipe handle, which is used when "
"communicating with the parent process.")
raise exception.GlanceException(err_msg)
# Get the length of the data to be received.
buff = self._ioutils.get_buffer(4)
self._ioutils.read_file(pipe_handle, buff, 4)
socket_buff_sz = struct.unpack('<I', buff)[0]
# Get the serialized socket object.
socket_buff = self._ioutils.get_buffer(socket_buff_sz)
self._ioutils.read_file(pipe_handle, socket_buff, socket_buff_sz)
self._ioutils.close_handle(pipe_handle)
# Recreate the socket object. This will only work with
# Python 3.6 or later.
return socket.fromshare(bytes(socket_buff[:]))
def configure_socket(self, old_conf=None, has_changed=None):
fresh_start = not (old_conf or has_changed)
pipe_handle = getattr(CONF, 'pipe_handle', None)
if not (fresh_start and pipe_handle):
return super(Win32Server, self).configure_socket(
old_conf, has_changed)
self.sock = self._get_sock_from_parent()
if hasattr(socket, 'TCP_KEEPIDLE'):
# This was introduced in WS 2016 RS3
self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE,
CONF.tcp_keepidle)
if os.name == 'nt':
Server = Win32Server
else:
Server = PosixServer
class Middleware(object): class Middleware(object):

View File

@ -51,7 +51,6 @@ _api_opts = [
glance.common.wsgi.eventlet_opts, glance.common.wsgi.eventlet_opts,
glance.common.wsgi.socket_opts, glance.common.wsgi.socket_opts,
glance.common.wsgi.store_opts, glance.common.wsgi.store_opts,
glance.common.wsgi.cli_opts,
glance.image_cache.drivers.sqlite.sqlite_opts, glance.image_cache.drivers.sqlite.sqlite_opts,
glance.image_cache.image_cache_opts, glance.image_cache.image_cache_opts,
glance.notifier.notifier_opts, glance.notifier.notifier_opts,

View File

@ -14,16 +14,9 @@
# under the License. # under the License.
import builtins import builtins
import os
import eventlet import eventlet
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()
import glance.async_ import glance.async_
# NOTE(danms): Default to eventlet threading for tests # NOTE(danms): Default to eventlet threading for tests

View File

@ -30,7 +30,6 @@ import platform
import shutil import shutil
import signal import signal
import socket import socket
import subprocess
import sys import sys
import tempfile import tempfile
from testtools import content as ttc from testtools import content as ttc
@ -42,7 +41,6 @@ import uuid
import fixtures import fixtures
import glance_store import glance_store
from os_win import utilsfactory as os_win_utilsfactory
from oslo_config import cfg from oslo_config import cfg
from oslo_serialization import jsonutils from oslo_serialization import jsonutils
import testtools import testtools
@ -58,12 +56,7 @@ 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'
SQLITE_CONN_TEMPLATE = 'sqlite:///%s/tests.sqlite'
else:
SQLITE_CONN_TEMPLATE = 'sqlite:////%s/tests.sqlite'
CONF = cfg.CONF CONF = cfg.CONF
@ -301,79 +294,7 @@ class PosixServer(BaseServer):
return (rc, '', '') return (rc, '', '')
class Win32Server(BaseServer): Server = PosixServer
def __init__(self, *args, **kwargs):
super(Win32Server, self).__init__(*args, **kwargs)
self._processutils = os_win_utilsfactory.get_processutils()
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):

View File

@ -137,7 +137,6 @@ class TestDriver(test_utils.BaseTestCase):
def create_images(self, images): def create_images(self, images):
for fixture in images: for fixture in images:
self.db_api.image_create(self.adm_context, fixture) self.db_api.image_create(self.adm_context, fixture)
self.delay_inaccurate_clock()
class DriverTests(object): class DriverTests(object):
@ -318,7 +317,6 @@ class DriverTests(object):
def test_image_update_properties(self): def test_image_update_properties(self):
fixture = {'properties': {'ping': 'pong'}} fixture = {'properties': {'ping': 'pong'}}
self.delay_inaccurate_clock()
image = self.db_api.image_update(self.adm_context, UUID1, fixture) image = self.db_api.image_update(self.adm_context, UUID1, fixture)
expected = {'ping': 'pong', 'foo': 'bar', 'far': 'boo'} expected = {'ping': 'pong', 'foo': 'bar', 'far': 'boo'}
actual = {p['name']: p['value'] for p in image['properties']} actual = {p['name']: p['value'] for p in image['properties']}
@ -1294,7 +1292,6 @@ class DriverTests(object):
'deleted': False} 'deleted': False}
self.assertEqual(expected, member) self.assertEqual(expected, member)
self.delay_inaccurate_clock()
member = self.db_api.image_member_update(self.context, member = self.db_api.image_member_update(self.context,
member_id, member_id,
{'can_share': True}) {'can_share': True})
@ -1338,7 +1335,6 @@ class DriverTests(object):
'deleted': False} 'deleted': False}
self.assertEqual(expected, member) self.assertEqual(expected, member)
self.delay_inaccurate_clock()
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'})
@ -1947,7 +1943,6 @@ class TaskTests(test_utils.BaseTestCase):
'status': 'processing', 'status': 'processing',
'message': 'This is a error string', 'message': 'This is a error string',
} }
self.delay_inaccurate_clock()
task = self.db_api.task_update(self.adm_context, task_id, fixture) task = self.db_api.task_update(self.adm_context, task_id, fixture)
self.assertEqual(task_id, task['id']) self.assertEqual(task_id, task['id'])
@ -1973,7 +1968,6 @@ class TaskTests(test_utils.BaseTestCase):
task_id = task['id'] task_id = task['id']
fixture = {'status': 'processing'} fixture = {'status': 'processing'}
self.delay_inaccurate_clock()
task = self.db_api.task_update(self.adm_context, task_id, fixture) task = self.db_api.task_update(self.adm_context, task_id, fixture)
self.assertEqual(task_id, task['id']) self.assertEqual(task_id, task['id'])

View File

@ -351,7 +351,6 @@ class TestCentralizedDb(functional.SynchronousAPIBase):
self.assertTrue(os.path.exists(incomplete_file_path)) self.assertTrue(os.path.exists(incomplete_file_path))
self.delay_inaccurate_clock()
self.driver.clean(stall_time=0) self.driver.clean(stall_time=0)
self.assertFalse(os.path.exists(incomplete_file_path)) self.assertFalse(os.path.exists(incomplete_file_path))

View File

@ -70,12 +70,6 @@ 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

@ -45,18 +45,11 @@ class TestWSGIServer(functional.FunctionalTest):
port = server.sock.getsockname()[1] port = server.sock.getsockname()[1]
def get_request(delay=0.0): def get_request(delay=0.0):
# Socket timeouts are handled rather inconsistently on Windows. sock = socket.socket()
# recv may either return nothing OR raise a ConnectionAbortedError. sock.connect(('127.0.0.1', port))
exp_exc = OSError if os.name == 'nt' else () time.sleep(delay)
sock.send(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
try: return sock.recv(1024)
sock = socket.socket()
sock.connect(('127.0.0.1', port))
time.sleep(delay)
sock.send(b'GET / HTTP/1.1\r\nHost: localhost\r\n\r\n')
return sock.recv(1024)
except exp_exc:
return None
# Should succeed - no timeout # Should succeed - no timeout
self.assertIn(greetings, get_request()) self.assertIn(greetings, get_request())

View File

@ -606,8 +606,6 @@ class ServerTest(test_utils.BaseTestCase):
def test_number_of_workers_posix(self, mock_migrate_db, mock_prefetcher): def test_number_of_workers_posix(self, mock_migrate_db, mock_prefetcher):
"""Ensure the number of workers matches num cpus limited to 8.""" """Ensure the number of workers matches num cpus limited to 8."""
mock_migrate_db.return_value = False mock_migrate_db.return_value = False
if os.name == 'nt':
raise self.skipException("Unsupported platform.")
def pid(): def pid():
i = 1 i = 1

View File

@ -578,7 +578,6 @@ class TestImageRepo(test_utils.BaseTestCase):
original_update_time = image.updated_at original_update_time = image.updated_at
image.name = 'foo' image.name = 'foo'
image.tags = ['king', 'kong'] image.tags = ['king', 'kong']
self.delay_inaccurate_clock()
self.image_repo.save(image) self.image_repo.save(image)
current_update_time = image.updated_at current_update_time = image.updated_at
self.assertGreater(current_update_time, original_update_time) self.assertGreater(current_update_time, original_update_time)
@ -636,7 +635,6 @@ class TestImageRepo(test_utils.BaseTestCase):
def test_remove_image(self): def test_remove_image(self):
image = self.image_repo.get(UUID1) image = self.image_repo.get(UUID1)
previous_update_time = image.updated_at previous_update_time = image.updated_at
self.delay_inaccurate_clock()
self.image_repo.remove(image) self.image_repo.remove(image)
self.assertGreater(image.updated_at, previous_update_time) self.assertGreater(image.updated_at, previous_update_time)
self.assertRaises(exception.ImageNotFound, self.image_repo.get, UUID1) self.assertRaises(exception.ImageNotFound, self.image_repo.get, UUID1)
@ -1053,7 +1051,6 @@ class TestTaskRepo(test_utils.BaseTestCase):
def test_save_task(self): def test_save_task(self):
task = self.task_repo.get(UUID1) task = self.task_repo.get(UUID1)
original_update_time = task.updated_at original_update_time = task.updated_at
self.delay_inaccurate_clock()
self.task_repo.save(task) self.task_repo.save(task)
current_update_time = task.updated_at current_update_time = task.updated_at
self.assertGreater(current_update_time, original_update_time) self.assertGreater(current_update_time, original_update_time)

View File

@ -139,7 +139,6 @@ class ImageCacheTestCase(object):
self.assertTrue(os.path.exists(invalid_file_path)) self.assertTrue(os.path.exists(invalid_file_path))
self.delay_inaccurate_clock()
if failure: if failure:
with mock.patch.object( with mock.patch.object(
fileutils, 'delete_if_exists') as mock_delete: fileutils, 'delete_if_exists') as mock_delete:
@ -167,7 +166,6 @@ class ImageCacheTestCase(object):
self.assertTrue(os.path.exists(incomplete_file_path)) self.assertTrue(os.path.exists(incomplete_file_path))
self.delay_inaccurate_clock()
self.cache.clean(stall_time=0) self.cache.clean(stall_time=0)
self.assertFalse(os.path.exists(incomplete_file_path)) self.assertFalse(os.path.exists(incomplete_file_path))

View File

@ -161,26 +161,6 @@ class BaseTestCase(testtools.TestCase):
self.addCleanup(patcher.stop) self.addCleanup(patcher.stop)
return result return result
def delay_inaccurate_clock(self, duration=0.001):
"""Add a small delay to compensate for inaccurate system clocks.
Some tests make assertions based on timestamps (e.g. comparing
'created_at' and 'updated_at' fields). In some cases, subsequent
time.time() calls may return identical values (python timestamps can
have a lower resolution on Windows compared to Linux - 1e-7 as
opposed to 1e-9).
A small delay (a few ms should be negligeable) can prevent such
issues. At the same time, it spares us from mocking the time
module, which might be undesired.
"""
# For now, we'll do this only for Windows. If really needed,
# on Py3 we can get the clock resolution using time.get_clock_info,
# but at that point we may as well just sleep 1ms all the time.
if os.name == 'nt':
time.sleep(duration)
class requires(object): class requires(object):
"""Decorator that initiates additional test setup/teardown.""" """Decorator that initiates additional test setup/teardown."""
@ -209,12 +189,8 @@ class depends_on_exe(object):
def __call__(self, func): def __call__(self, func):
def _runner(*args, **kw): def _runner(*args, **kw):
if os.name != 'nt': exitcode, out, err = execute('which %s' % self.exe,
cmd = 'which %s' % self.exe raise_error=False)
else:
cmd = 'where.exe', '%s' % self.exe
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
args[0].disabled = True args[0].disabled = True
@ -394,10 +370,7 @@ 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
if os.name != 'nt': args = shlex.split(cmd)
args = shlex.split(cmd)
else:
args = cmd
executable = args[0] executable = args[0]
if os.path.isabs(executable): if os.path.isabs(executable):

View File

@ -0,0 +1,5 @@
---
upgrade:
- |
Support for running glance services in Windows operating systems has been
removed.

View File

@ -51,6 +51,4 @@ cursive>=0.2.1 # Apache-2.0
# timeutils # timeutils
iso8601>=0.1.11 # MIT iso8601>=0.1.11 # MIT
os-win>=4.0.1 # Apache-2.0
castellan>=0.17.0 # Apache-2.0 castellan>=0.17.0 # Apache-2.0