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
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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

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
access times tracked for all files (in other words, the noatime option
CANNOT be set for that filesystem). In addition, ``user_xattr`` must be
set on the filesystem's description line in fstab. Because of these
requirements, the ``xattr`` cache driver is not available on Windows.
set on the filesystem's description line in fstab.
``image_cache_sqlite_db=DB_FILE``
Optional.

View File

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

View File

@ -23,13 +23,7 @@ import os
import sys
import eventlet
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()
eventlet.patcher.monkey_patch()
# Monkey patch the original current_thread to use the up-to-date _active
# 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)
import glance_store
from os_win import utilsfactory as os_win_utilsfactory
from oslo_config import cfg
from oslo_log import log as logging
@ -64,18 +57,7 @@ CONF.set_default(name='use_stderr', default=True)
def main():
# Used on Window, ensuring that a single scrubber can run at a time.
mutex = None
mutex_acquired = False
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_opts(scrubber.scrubber_cmd_opts)
@ -99,12 +81,7 @@ def main():
app = scrubber.Scrubber(glance_store)
if CONF.restore:
if os.name == 'nt':
scrubber_already_running = not mutex_acquired
else:
scrubber_already_running = scrubber_already_running_posix()
if scrubber_already_running:
if scrubber_already_running():
already_running_msg = (
"ERROR: glance-scrubber is already running. "
"Please ensure that the daemon is stopped.")
@ -121,12 +98,9 @@ def main():
sys.exit("ERROR: %s" % e)
except RuntimeError as 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.
# 1. Try to find the pid file if scrubber is controlled by
# glance-control

View File

@ -24,10 +24,7 @@ import abc
import errno
import functools
import os
import re
import signal
import struct
import subprocess
import sys
import time
@ -35,7 +32,6 @@ from eventlet.green import socket
import eventlet.greenio
import eventlet.wsgi
import glance_store
from os_win import utilsfactory as os_win_utilsfactory
from oslo_concurrency import processutils
from oslo_config import cfg
from oslo_log import log as logging
@ -252,13 +248,6 @@ store_opts = [
'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__)
@ -286,17 +275,9 @@ RESERVED_STORES = {
}
def register_cli_opts():
CONF.register_cli_opts(cli_opts)
def get_num_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:
# None implies the number of CPUs limited to 8
# See Launchpad bug #1748916 and the config help text
@ -740,125 +721,7 @@ class PosixServer(BaseServer):
self.start_wsgi()
class Win32ProcessLauncher(object):
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
Server = PosixServer
class Middleware(object):

View File

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

View File

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

View File

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

View File

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

View File

@ -45,18 +45,11 @@ class TestWSGIServer(functional.FunctionalTest):
port = server.sock.getsockname()[1]
def get_request(delay=0.0):
# Socket timeouts are handled rather inconsistently on Windows.
# recv may either return nothing OR raise a ConnectionAbortedError.
exp_exc = OSError if os.name == 'nt' else ()
try:
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
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)
# Should succeed - no timeout
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):
"""Ensure the number of workers matches num cpus limited to 8."""
mock_migrate_db.return_value = False
if os.name == 'nt':
raise self.skipException("Unsupported platform.")
def pid():
i = 1

View File

@ -578,7 +578,6 @@ class TestImageRepo(test_utils.BaseTestCase):
original_update_time = image.updated_at
image.name = 'foo'
image.tags = ['king', 'kong']
self.delay_inaccurate_clock()
self.image_repo.save(image)
current_update_time = image.updated_at
self.assertGreater(current_update_time, original_update_time)
@ -636,7 +635,6 @@ class TestImageRepo(test_utils.BaseTestCase):
def test_remove_image(self):
image = self.image_repo.get(UUID1)
previous_update_time = image.updated_at
self.delay_inaccurate_clock()
self.image_repo.remove(image)
self.assertGreater(image.updated_at, previous_update_time)
self.assertRaises(exception.ImageNotFound, self.image_repo.get, UUID1)
@ -1053,7 +1051,6 @@ class TestTaskRepo(test_utils.BaseTestCase):
def test_save_task(self):
task = self.task_repo.get(UUID1)
original_update_time = task.updated_at
self.delay_inaccurate_clock()
self.task_repo.save(task)
current_update_time = task.updated_at
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.delay_inaccurate_clock()
if failure:
with mock.patch.object(
fileutils, 'delete_if_exists') as mock_delete:
@ -167,7 +166,6 @@ class ImageCacheTestCase(object):
self.assertTrue(os.path.exists(incomplete_file_path))
self.delay_inaccurate_clock()
self.cache.clean(stall_time=0)
self.assertFalse(os.path.exists(incomplete_file_path))

View File

@ -161,26 +161,6 @@ class BaseTestCase(testtools.TestCase):
self.addCleanup(patcher.stop)
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):
"""Decorator that initiates additional test setup/teardown."""
@ -209,12 +189,8 @@ class depends_on_exe(object):
def __call__(self, func):
def _runner(*args, **kw):
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('which %s' % self.exe,
raise_error=False)
if exitcode != 0:
args[0].disabled_message = 'test requires exe: %s' % self.exe
args[0].disabled = True
@ -394,10 +370,7 @@ def execute(cmd,
path_ext = [os.path.join(os.getcwd(), 'bin')]
# Also jack in the path cmd comes from, if it's absolute
if os.name != 'nt':
args = shlex.split(cmd)
else:
args = cmd
args = shlex.split(cmd)
executable = args[0]
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
iso8601>=0.1.11 # MIT
os-win>=4.0.1 # Apache-2.0
castellan>=0.17.0 # Apache-2.0