From acc8186163246cbe8539cd0fed7d3613f24e49e7 Mon Sep 17 00:00:00 2001 From: Mark Syms Date: Thu, 3 Oct 2024 10:04:11 +0100 Subject: [PATCH] CP-51658: install script to allow stopping all in-progress GC operations Signed-off-by: Mark Syms --- Makefile | 2 ++ drivers/cleanup.py | 20 +++++++++++++++++++- mk/sm.spec.in | 1 + scripts/stop_all_gc | 7 +++++++ tests/test_cleanup.py | 27 ++++++++++++++++++++++++++- 5 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 scripts/stop_all_gc diff --git a/Makefile b/Makefile index ad81be401..e6ff5a7a0 100755 --- a/Makefile +++ b/Makefile @@ -202,6 +202,8 @@ install: precheck install -m 755 scripts/usb_change $(SM_STAGING)$(LIBEXEC) install -m 755 scripts/kickpipe $(SM_STAGING)$(LIBEXEC) install -m 755 scripts/set-iscsi-initiator $(SM_STAGING)$(LIBEXEC) + mkdir -p $(SM_STAGING)/etc/xapi.d/xapi-pre-shutdown/ + install -m 755 scripts/stop_all_gc $(SM_STAGING)/etc/xapi.d/xapi-pre-shutdown/ $(MAKE) -C dcopy install DESTDIR=$(SM_STAGING) ln -sf $(SM_DEST)blktap2.py $(SM_STAGING)$(BIN_DEST)/blktap2 ln -sf $(SM_DEST)lcache.py $(SM_STAGING)$(BIN_DEST)tapdisk-cache-stats diff --git a/drivers/cleanup.py b/drivers/cleanup.py index 5a22544c0..9d5172e50 100755 --- a/drivers/cleanup.py +++ b/drivers/cleanup.py @@ -82,11 +82,22 @@ NON_PERSISTENT_DIR = '/run/nonpersistent/sm' +# Signal Handler +SIGTERM = False + class AbortException(util.SMException): pass +def receiveSignal(signalNumber, frame): + global SIGTERM + + util.SMlog("GC: recieved SIGTERM") + SIGTERM = True + return + + ################################################################################ # # Util @@ -167,7 +178,7 @@ def runAbortable(func, ret, ns, abortTest, pollInterval, timeOut): if resultFlag.test("failure"): resultFlag.clear("failure") raise util.SMException("Child process exited with error") - if abortTest() or abortSignaled: + if abortTest() or abortSignaled or SIGTERM: os.killpg(pid, signal.SIGKILL) raise AbortException("Aborting due to signal") if timeOut and _time() - startTime > timeOut: @@ -2966,6 +2977,10 @@ def _gcLoop(sr, dryRun=False, immediate=False): "Garbage collection for SR %s" % sr.uuid) _gcLoopPause(sr, dryRun, immediate=immediate) while True: + if SIGTERM: + Util.log("Term requested") + return + if not sr.xapi.isPluggedHere(): Util.log("SR no longer attached, exiting") break @@ -3191,6 +3206,9 @@ def gc(session, srUuid, inBackground, dryRun=False): 6. If there is something to coalesce, coalesce one pair, then goto 3 """ Util.log("=== SR %s: gc ===" % srUuid) + + signal.signal(signal.SIGTERM, receiveSignal) + if inBackground: if daemonize(): # we are now running in the background. Catch & log any errors diff --git a/mk/sm.spec.in b/mk/sm.spec.in index ac33116a3..f06829990 100755 --- a/mk/sm.spec.in +++ b/mk/sm.spec.in @@ -116,6 +116,7 @@ tests/run_python_unittests.sh /etc/xapi.d/plugins/testing-hooks /etc/xapi.d/plugins/intellicache-clean /etc/xapi.d/plugins/trim +/etc/xapi.d/xapi-pre-shutdown/* /etc/xensource/master.d/02-vhdcleanup /opt/xensource/bin/blktap2 /opt/xensource/bin/tapdisk-cache-stats diff --git a/scripts/stop_all_gc b/scripts/stop_all_gc new file mode 100644 index 000000000..38ae3db87 --- /dev/null +++ b/scripts/stop_all_gc @@ -0,0 +1,7 @@ +#!/bin/bash + +/usr/bin/systemctl list-units SMGC@* --all --no-legend | /usr/bin/cut -d ' ' -f1 | sed 's/\\/\\\\/g' | while read service; +do + echo "Stopping $service" + /usr/bin/systemctl stop "$service" +done diff --git a/tests/test_cleanup.py b/tests/test_cleanup.py index 96448b55e..19bbf679a 100644 --- a/tests/test_cleanup.py +++ b/tests/test_cleanup.py @@ -1,4 +1,5 @@ import errno +import signal import unittest import unittest.mock as mock @@ -40,7 +41,8 @@ def acquireNoblock(self): class TestRelease(object): - pass + def acquireNoblock(self): + return True class IrrelevantLock(object): @@ -72,6 +74,9 @@ def setUp(self): self.addCleanup(mock.patch.stopall) + def tearDown(self): + cleanup.SIGTERM = False + def setup_abort_flag(self, ipc_mock, should_abort=False): flag = mock.Mock() flag.test = mock.Mock(return_value=should_abort) @@ -89,6 +94,26 @@ def mock_cleanup_locks(self): cleanup.lockGCRunning = TestRelease() cleanup.lockGCRunning.release = mock.Mock(return_value=None) + def test_term_handler(self): + self.assertFalse(cleanup.SIGTERM) + + cleanup.receiveSignal(signal.SIGTERM, None) + + self.assertTrue(cleanup.SIGTERM) + + @mock.patch('cleanup._create_init_file', autospec=True) + @mock.patch('cleanup.SR', autospec=True) + @mock.patch('cleanup._ensure_xapi_initialised', autospec=True) + def test_loop_exits_on_term(self, mock_init, mock_sr, mock_check_xapi): + # Set the term signel + cleanup.receiveSignal(signal.SIGTERM, None) + mock_session = mock.MagicMock(name='MockSession') + sr_uuid = str(uuid4()) + self.mock_cleanup_locks() + + # Trigger GC + cleanup.gc(mock_session, sr_uuid, inBackground=False) + def test_lock_if_already_locked(self): """ Given an already locked SR, a lock call