From f5dce0bf107cd18bcecaf9bb5b2ee967c48598a7 Mon Sep 17 00:00:00 2001 From: Guillaume Raffy Date: Wed, 1 Apr 2026 17:31:45 +0200 Subject: [PATCH] v 1.0.11 - added IGridEngine abstraction that allows to switch between dry-run mode and normal mode - added more commandes to the test work related to [https://bugzilla.ipr.univ-rennes.fr/show_bug.cgi?id=3093] --- cocluto/quman.py | 51 +++++++++++++++++++++++++++++++++++----------- cocluto/version.py | 2 +- test/test_quman.py | 12 +++++++---- 3 files changed, 48 insertions(+), 17 deletions(-) diff --git a/cocluto/quman.py b/cocluto/quman.py index e0ec79e..4a0f118 100755 --- a/cocluto/quman.py +++ b/cocluto/quman.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +from abc import ABC, abstractmethod from typing import Dict import subprocess import argparse @@ -11,14 +12,36 @@ RequesterId = str # identifies the queue enable/disable requester eg auto.croco QueueMachineId = str # identifies the queue machine eg main.q@alambix42.ipr.univ-rennes.fr -def run_qmod(args): - """runs qmod with the given arguments.""" - cmd = ["qmod"] + args - dry_run = True # set to True to print the command instead of executing it - if dry_run: - print(f"Dry run: {' '.join(cmd)}") - else: - subprocess.run(cmd, check=True) +class IGridEngine(ABC): + + @abstractmethod + def disable_queue(self, queue_name: QueueMachineId): + pass + + @abstractmethod + def enable_queue(self, queue_name: QueueMachineId): + pass + + +class Sge(IGridEngine): + dry_run: bool + + def __init__(self, dry_run: bool = False): + self.dry_run = dry_run + + def run_qmod(self, args): + """runs qmod with the given arguments.""" + cmd = ["qmod"] + args + if self.dry_run: + print(f"Dry run: {' '.join(cmd)}") + else: + subprocess.run(cmd, check=True) + + def disable_queue(self, queue_name: QueueMachineId): + self.run_qmod(["-d", queue_name]) + + def enable_queue(self, queue_name: QueueMachineId): + self.run_qmod(["-e", queue_name]) def init_db(db_backend: ISqlDatabaseBackend): @@ -69,8 +92,12 @@ class DisableReason: class QueueManager: - def __init__(self, db_backend: ISqlDatabaseBackend): + db_backend: ISqlDatabaseBackend + grid_engine: IGridEngine + + def __init__(self, db_backend: ISqlDatabaseBackend, grid_engine: IGridEngine = Sge()): self.db_backend = db_backend + self.grid_engine = grid_engine def log_modification(self, queue_name: str, action: str, requester_id: RequesterId, reason: str) -> LogId: assert action in ["disable", "enable"], "Action must be either 'disable' or 'enable'" @@ -96,7 +123,7 @@ class QueueManager: if len(disable_reasons) == 0: # queue is currently active, we can disable it - run_qmod(["-d", queue_name]) + self.grid_engine.disable_queue(queue_name) disable_log_id = self.log_modification(queue_name, "disable", requester_id, reason) self.db_backend.query(f"INSERT INTO state (disable_reason_id, queue_name) VALUES ({disable_log_id}, '{queue_name}');") @@ -111,7 +138,7 @@ class QueueManager: assert dr_to_remove is not None, f"Requester {requester_id} has not requested deactivation of queue {queue_name}. Cannot request activation without a prior deactivation." - run_qmod(["-e", queue_name]) + self.grid_engine.enable_queue(queue_name) enable_log_id = self.log_modification(queue_name, "enable", requester_id, reason) # noqa: F841 self.db_backend.query(f"DELETE FROM state WHERE disable_reason_id = {dr_to_remove.log_id} AND queue_name = '{queue_name}';") @@ -126,7 +153,7 @@ def main(): args = parser.parse_args() db_backend = create_db_backend() - qmod = QueueManager(db_backend) + qmod = QueueManager(db_backend, Sge(dry_run=False)) # set dry_run to False to actually run qmod commands queue = args.queue diff --git a/cocluto/version.py b/cocluto/version.py index 525f7de..7dca1b7 100644 --- a/cocluto/version.py +++ b/cocluto/version.py @@ -1,4 +1,4 @@ -__version__ = '1.0.10' +__version__ = '1.0.11' class Version(object): diff --git a/test/test_quman.py b/test/test_quman.py index 697fcbc..3d113b8 100644 --- a/test/test_quman.py +++ b/test/test_quman.py @@ -3,7 +3,7 @@ import unittest import logging # from cocluto import ClusterController from cocluto.SimpaDbUtil import SqliteDb -from cocluto.quman import QueueManager, init_db +from cocluto.quman import QueueManager, init_db, Sge class QumanTestCase(unittest.TestCase): @@ -20,9 +20,13 @@ class QumanTestCase(unittest.TestCase): db_path.unlink() db_backend = SqliteDb(db_path) init_db(db_backend) - quman = QueueManager(db_backend) - quman.request_queue_deactivation('main.q@alambix42', 'sysadmin.graffy', 'because I want to test quman') - quman.request_queue_activation('main.q@alambix42', 'sysadmin.graffy', 'because I want to test quman') + quman = QueueManager(db_backend, Sge(dry_run=True)) # set dry_run to True to not actually run qmod commands + quman.request_queue_deactivation('main.q@alambix42', 'sysadmin.graffy', 'disabled to move the alambix42 to another rack') + with self.assertRaises(AssertionError): + # attempting to disable the same queue again with the same disable tag should raise an assertion error (the tag is used to uniquely identify the disables on the machine) + quman.request_queue_deactivation('main.q@alambix42', 'sysadmin.graffy', 'because I want to test quman') + quman.request_queue_deactivation('main.q@alambix42', 'croconaus.maco-update', 'disabled to update maco') + quman.request_queue_activation('main.q@alambix42', 'sysadmin.graffy', 'alambix42 has been moved to a new rack') # self.assertIsInstance(job_state, JobsState) db_backend.dump(Path('./quman_test/quman_dump.sql'))