From fa775c6d060fcc3024abdab34ef02ab183c48256 Mon Sep 17 00:00:00 2001 From: Federico Stagni Date: Tue, 15 Nov 2022 15:36:24 +0100 Subject: [PATCH] feat: removed old PilotsLogging machinery --- .../PilotsLogging/PilotsLoggingDB.png | Bin 16725 -> 0 bytes .../PilotsLogging/PilotsLoggingDiagram.png | Bin 31251 -> 0 bytes .../PilotsLogging/index.rst | 74 ------- .../Systems/WorkloadManagement/index.rst | 1 - .../Systems/WorkloadManagement/index.rst | 6 - .../Agent/SiteDirector.py | 5 - .../Client/PilotsLoggingClient.py | 45 ----- .../ConfigTemplate.cfg | 16 +- .../DB/PilotsLoggingDB.py | 190 ------------------ .../DB/PilotsLoggingDB.sql | 2 - .../Service/JobManagerHandler.py | 23 +-- .../Service/PilotManagerHandler.py | 40 +--- .../Service/PilotsLoggingHandler.py | 102 ---------- .../scripts/dirac_admin_pilot_logging_info.py | 61 ------ .../Test_PilotsLoggingClient.py | 114 ----------- .../all_integration_client_tests.sh | 1 - tests/Jenkins/utilities.sh | 2 +- 17 files changed, 6 insertions(+), 676 deletions(-) delete mode 100644 docs/source/AdministratorGuide/Systems/WorkloadManagement/PilotsLogging/PilotsLoggingDB.png delete mode 100644 docs/source/AdministratorGuide/Systems/WorkloadManagement/PilotsLogging/PilotsLoggingDiagram.png delete mode 100644 docs/source/AdministratorGuide/Systems/WorkloadManagement/PilotsLogging/index.rst delete mode 100644 src/DIRAC/WorkloadManagementSystem/Client/PilotsLoggingClient.py delete mode 100644 src/DIRAC/WorkloadManagementSystem/DB/PilotsLoggingDB.py delete mode 100644 src/DIRAC/WorkloadManagementSystem/DB/PilotsLoggingDB.sql delete mode 100644 src/DIRAC/WorkloadManagementSystem/Service/PilotsLoggingHandler.py delete mode 100755 src/DIRAC/WorkloadManagementSystem/scripts/dirac_admin_pilot_logging_info.py delete mode 100644 tests/Integration/WorkloadManagementSystem/Test_PilotsLoggingClient.py diff --git a/docs/source/AdministratorGuide/Systems/WorkloadManagement/PilotsLogging/PilotsLoggingDB.png b/docs/source/AdministratorGuide/Systems/WorkloadManagement/PilotsLogging/PilotsLoggingDB.png deleted file mode 100644 index ee54b10f93606827782cb396912e17742e906b11..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16725 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

WpEOXd)c=uks_^)|&9znACjvu*l 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 diff --git a/docs/source/AdministratorGuide/Systems/WorkloadManagement/PilotsLogging/index.rst b/docs/source/AdministratorGuide/Systems/WorkloadManagement/PilotsLogging/index.rst deleted file mode 100644 index f2efac53728..00000000000 --- a/docs/source/AdministratorGuide/Systems/WorkloadManagement/PilotsLogging/index.rst +++ /dev/null @@ -1,74 +0,0 @@ -.. contents:: Table of contents - :depth: 3 - -======================================================================================= - -======================================================================================= - -============================== -Pilots Logging system overview -============================== - -Pilots Loggins system is designed to allow logging of pilot state on every stage of lifecycle, including before installing -DIRAC client and starting pilot process. - -Each logging entry includes: - -- current status of the Pilot - has to be one of predefined list of possible states, -- additional information about status, -- timestamp of logging the status - if there is no timestamp of actual event provided, time of adding entry to database will be used, -- source of the logging message to distinguish updates from Pilot itself and other services. - -.. image:: PilotsLoggingDiagram.png - :alt: PilotsLogging system - :align: center - - -Server side -================================ - -Server elements of Pilots Logging system is build using five elements: - -- message queue (RabbitMQ) server, -- message queue consumer, -- DIRAC Client, -- DIRAC Service, -- database. - -Message queue --------------------------------- - -Message works as a interface between Pilot and Pilots Logging service. Pilot puts status related messages into queue then -messages are handled by message queue consumer. - -Message queue consumer --------------------------------- - -Consumer registers itself into message queue. When new messages arrive they are handled by callback function. In consumer -messages are processed and passed to DIRAC Service using DIRAC Client. - -DIRAC Client --------------------------------- - -Client handles RPC communication with Service. This is 'thin-client', all business logic is in Service. - -DIRAC Service --------------------------------- - -Service exports functions to be called by Clients. It handles all operations on databases. All server side logic of -Pilots Logging system is defined here. Two databases are accessed to gather all required information. - -Database --------------------------------- - -Database class handles operation on the database. Object-relational mapping is done using SQLAlchemy. Single table stores -record for every status reported by Pilot: - -.. image:: PilotsLoggingDB.png - :alt: PilotsLogging database schema - :align: center - -Pilot side -================================ - -TBD diff --git a/docs/source/AdministratorGuide/Systems/WorkloadManagement/index.rst b/docs/source/AdministratorGuide/Systems/WorkloadManagement/index.rst index fb06f7cd52e..2b0ab2319a7 100644 --- a/docs/source/AdministratorGuide/Systems/WorkloadManagement/index.rst +++ b/docs/source/AdministratorGuide/Systems/WorkloadManagement/index.rst @@ -83,7 +83,6 @@ The following sections add some detail for the WMS systems. architecture Pilots/index Pilots/Pilots3 - PilotsLogging/index Jobs/index JobsPriorities JobsMatching diff --git a/docs/source/DeveloperGuide/Systems/WorkloadManagement/index.rst b/docs/source/DeveloperGuide/Systems/WorkloadManagement/index.rst index 40e0923f26a..8a961a2ec11 100644 --- a/docs/source/DeveloperGuide/Systems/WorkloadManagement/index.rst +++ b/docs/source/DeveloperGuide/Systems/WorkloadManagement/index.rst @@ -78,7 +78,6 @@ It is based on layered architecture and is based on DIRAC architecture: * JobStateUpdateHandler * MatcherHandler * OptimizationMindHandler - * PilotsLoggingHandler * SandboxStoreHandler * WMSAdministratorHandler * WMSUtilities @@ -97,11 +96,6 @@ It is based on layered architecture and is based on DIRAC architecture: This database keeps track of all the submitted grid pilot jobs. It also registers the mapping of the DIRAC jobs to the pilot agents. - * PilotsLoggingDB: - PilotsLoggingDB class is a front-end to the Pilots Logging Database. - This database keeps track of all the submitted grid pilot jobs. - It also registers the mapping of the DIRAC jobs to the pilot agents. - * SandboxMetadataDB SandboxMetadataDB class is a front-end to the metadata for sandboxes. diff --git a/src/DIRAC/WorkloadManagementSystem/Agent/SiteDirector.py b/src/DIRAC/WorkloadManagementSystem/Agent/SiteDirector.py index c0b447a391a..eedfca8980b 100644 --- a/src/DIRAC/WorkloadManagementSystem/Agent/SiteDirector.py +++ b/src/DIRAC/WorkloadManagementSystem/Agent/SiteDirector.py @@ -984,11 +984,6 @@ def _getPilotOptions(self, queue, **kwargs): else: self.log.info("DIRAC project will be installed by pilots") - # Pilot Logging defined? - pilotLogging = opsHelper.getValue("/Services/JobMonitoring/usePilotsLoggingFlag", False) - if pilotLogging: - pilotOptions.append("-z ") - pilotOptions.append("--pythonVersion=3") # Debug diff --git a/src/DIRAC/WorkloadManagementSystem/Client/PilotsLoggingClient.py b/src/DIRAC/WorkloadManagementSystem/Client/PilotsLoggingClient.py deleted file mode 100644 index 574b8a23abb..00000000000 --- a/src/DIRAC/WorkloadManagementSystem/Client/PilotsLoggingClient.py +++ /dev/null @@ -1,45 +0,0 @@ -""" Class that contains client access to the PilotsLogging handler. """ - -from DIRAC.Core.Base.Client import Client, createClient - - -@createClient("WorkloadManagement/PilotsLogging") -class PilotsLoggingClient(Client): - """Implementation of interface of Pilots Logging service. Client class should be used to communicate - with PilotsLogging Service - """ - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.setServer("WorkloadManagement/PilotsLogging") - - def addPilotsLogging(self, pilotUUID, timestamp, source, phase, status, messageContent): - """ - Add new Pilots Logging entry - - :param pilotUUID: Pilot reference - :param status: Pilot status - :param minorStatus: Additional status information - :param timestamp: Date and time of status event - :param source: Source of status information - """ - - return self._getRPC().addPilotsLogging(pilotUUID, timestamp, source, phase, status, messageContent) - - def deletePilotsLogging(self, pilotUUID): - """ - Delete all Logging entries for Pilot - - :param pilotUUID: Pilot reference - """ - - return self._getRPC().deletePilotsLogging(pilotUUID) - - def getPilotsLogging(self, pilotUUID): - """ - Get all Logging entries for Pilot - - :param pilotUUID: Pilot reference - """ - - return self._getRPC().getPilotsLogging(pilotUUID) diff --git a/src/DIRAC/WorkloadManagementSystem/ConfigTemplate.cfg b/src/DIRAC/WorkloadManagementSystem/ConfigTemplate.cfg index 2d39398123e..d72948d2967 100644 --- a/src/DIRAC/WorkloadManagementSystem/ConfigTemplate.cfg +++ b/src/DIRAC/WorkloadManagementSystem/ConfigTemplate.cfg @@ -102,7 +102,7 @@ Services } } ##END - #Parameters of the Pilots service + #Parameters of the PilotManager service PilotManager { Port = 9171 @@ -111,20 +111,6 @@ Services Default = authenticated } } - #Parameters of the PilotsLogging service - PilotsLogging - { - Port = 9146 - Authorization - { - Default = Operator - getPilotsLogging = authenticated - addPilotsLogging = Operator - deletePilotsLogging = Operator - } - Enable = No - PilotsLoggingQueue = serviceURL::QueueType::QueueName - } ##BEGIN SandboxStore SandboxStore { diff --git a/src/DIRAC/WorkloadManagementSystem/DB/PilotsLoggingDB.py b/src/DIRAC/WorkloadManagementSystem/DB/PilotsLoggingDB.py deleted file mode 100644 index 3635ac127d7..00000000000 --- a/src/DIRAC/WorkloadManagementSystem/DB/PilotsLoggingDB.py +++ /dev/null @@ -1,190 +0,0 @@ -""" PilotsLoggingDB class is a front-end to the Pilots Logging Database. - This database keeps track of all the submitted grid pilot jobs. - It also registers the mapping of the DIRAC jobs to the pilot - agents. - - Available methods are: - - addPilotsLogging() - getPilotsLogging() - deletePilotsLoggin() - -""" - -from sqlalchemy.orm import sessionmaker, scoped_session -from sqlalchemy.engine.reflection import Inspector -from sqlalchemy import create_engine, Column, MetaData, Integer, String -from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.exc import SQLAlchemyError - -from DIRAC import gLogger, S_OK, S_ERROR -from DIRAC.Core.Utilities import DErrno -from DIRAC.ConfigurationSystem.Client.Utilities import getDBParameters -from DIRAC.Core.Utilities.ObjectLoader import ObjectLoader - -TABLESLIST = ["PilotsLogging"] - -metadata = MetaData() -Base = declarative_base() - - -class PilotsLoggingDB: - def __init__(self, parentLogger=None): - - if not parentLogger: - parentLogger = gLogger - self.log = parentLogger.getSubLogger(self.__class__.__name__) - - result = getDBParameters("WorkloadManagement/PilotsLoggingDB") - if not result["OK"]: - raise RuntimeError("Cannot get database parameters: %s" % result["Message"]) - - dbParameters = result["Value"] - self.dbHost = dbParameters["Host"] - self.dbPort = dbParameters["Port"] - self.dbUser = dbParameters["User"] - self.dbPass = dbParameters["Password"] - self.dbName = dbParameters["DBName"] - - self.objectLoader = ObjectLoader() - - # These are the list of tables that will be created. - # They can be extended in an extension module - result = self.objectLoader.loadObject(__name__, "TABLESLIST") - if not result["OK"]: - raise Exception(result["Message"]) - self.tablesList = result["Value"] - - self.__initializeConnection() - resp = self.__initializeDB() - if not resp["OK"]: - raise Exception("Couldn't create tables: " + resp["Message"]) - - ########################################################################################## - - def __initializeConnection(self): - """ - This should be in a base class eventually - """ - - self.engine = create_engine( - f"mysql://{self.dbUser}:{self.dbPass}@{self.dbHost}:{self.dbPort}/{self.dbName}", - pool_recycle=3600, - echo_pool=True, - echo=self.log.getLevel() == "DEBUG", - ) - self.sqlalchemySession = scoped_session(sessionmaker(bind=self.engine)) - self.inspector = Inspector.from_engine(self.engine) - - ########################################################################################## - def __initializeDB(self): - """ - Create the tables, if they are not there yet - """ - - tablesInDB = self.inspector.get_table_names() - - for table in self.tablesList: - if table not in tablesInDB: - result = self.objectLoader.loadObject(__name__, table) - if not result["OK"]: - return result - result["Value"].__table__.create(self.engine) - else: - gLogger.debug(f"Table {table} already exists") - - return S_OK() - - ########################################################################################## - def addPilotsLogging(self, pilotUUID, timestamp, source, phase, status, messageContent): - """Add new pilot logging entry""" - - session = self.sqlalchemySession() - logging = PilotsLogging(pilotUUID, timestamp, source, phase, status, messageContent) - - try: - session.add(logging) - except SQLAlchemyError as e: - session.rollback() - session.close() - return S_ERROR(DErrno.ESQLA, "Failed to add PilotsLogging: " + str(e)) - - try: - session.commit() - except SQLAlchemyError as e: - session.rollback() - session.close() - return S_ERROR(DErrno.ESQLA, "Failed to commit PilotsLogging: " + str(e)) - - return S_OK() - - ########################################################################################## - def getPilotsLogging(self, pilotUUID): - """Get list of logging entries for pilot""" - - session = self.sqlalchemySession() - - pilotLogging = [] - for pl in ( - session.query(PilotsLogging) - .filter(PilotsLogging.pilotUUID == pilotUUID) - .order_by(PilotsLogging.timestamp) - .all() - ): - entry = {} - entry["pilotUUID"] = pl.pilotUUID - entry["timestamp"] = pl.timestamp - entry["source"] = pl.source - entry["phase"] = pl.phase - entry["status"] = pl.status - entry["messageContent"] = pl.messageContent - pilotLogging.append(entry) - - return S_OK(pilotLogging) - - ########################################################################################## - def deletePilotsLogging(self, pilotUUID): - """Delete all logging entries for pilot""" - - if isinstance(pilotUUID, str): - pilotUUID = [ - pilotUUID, - ] - - session = self.sqlalchemySession() - - session.query(PilotsLogging).filter(PilotsLogging.pilotUUID.in_(pilotUUID)).delete(synchronize_session="fetch") - - try: - session.commit() - except SQLAlchemyError as e: - session.rollback() - session.close() - return S_ERROR(DErrno.ESQLA, "Failed to commit: " + str(e)) - - return S_OK() - - ########################################################################################## - - -class PilotsLogging(Base): - """PilotsLogging table""" - - __tablename__ = "PilotsLogging" - __table_args__ = {"mysql_engine": "InnoDB", "mysql_charset": "utf8"} - - logID = Column("LogID", Integer, primary_key=True, autoincrement=True) - pilotUUID = Column("pilotUUID", String(255), nullable=False) - timestamp = Column("timestamp", String(255), nullable=False) - source = Column("source", String(255), nullable=False) - phase = Column("phase", String(255), nullable=False) - status = Column("status", String(255), nullable=False) - messageContent = Column("messageContent", String(255), nullable=False) - - def __init__(self, pilotUUID, timestamp, source, phase, status, messageContent): - self.pilotUUID = pilotUUID - self.timestamp = timestamp - self.source = source - self.phase = phase - self.status = status - self.messageContent = messageContent diff --git a/src/DIRAC/WorkloadManagementSystem/DB/PilotsLoggingDB.sql b/src/DIRAC/WorkloadManagementSystem/DB/PilotsLoggingDB.sql deleted file mode 100644 index ef116dde1c0..00000000000 --- a/src/DIRAC/WorkloadManagementSystem/DB/PilotsLoggingDB.sql +++ /dev/null @@ -1,2 +0,0 @@ -# Everything is created by the DB object upon instantiatiation if it does not exists. -use PilotsLoggingDB; diff --git a/src/DIRAC/WorkloadManagementSystem/Service/JobManagerHandler.py b/src/DIRAC/WorkloadManagementSystem/Service/JobManagerHandler.py index 8808e9baf93..7661bdd81e7 100755 --- a/src/DIRAC/WorkloadManagementSystem/Service/JobManagerHandler.py +++ b/src/DIRAC/WorkloadManagementSystem/Service/JobManagerHandler.py @@ -63,17 +63,6 @@ def initializeHandler(cls, serviceInfoDict): except RuntimeError as excp: return S_ERROR("Can't connect to DB: %s" % excp) - cls.pilotsLoggingDB = None - enablePilotsLogging = Operations().getValue("/Services/JobMonitoring/usePilotsLoggingFlag", False) - if enablePilotsLogging: - try: - result = ObjectLoader().loadObject("WorkloadManagementSystem.DB.PilotsLoggingDB", "PilotsLoggingDB") - if not result["OK"]: - return result - cls.pilotsLoggingDB = result["Value"](parentLogger=cls.log) - except RuntimeError as excp: - return S_ERROR("Can't connect to DB: %s" % excp) - cls.msgClient = MessageClient("WorkloadManagement/OptimizationMind") result = cls.msgClient.connect(JobManager=True) if not result["OK"]: @@ -431,8 +420,7 @@ def export_removeJob(self, jobIDs): return S_OK(validJobList) def __deleteJob(self, jobID): - """Set the job status to "Deleted" - and remove the pilot that ran and its logging info if the pilot is finished. + """Set the job status to "Deleted" and remove the pilot that ran. :param int jobID: job ID :return: S_OK()/S_ERROR() @@ -445,7 +433,7 @@ def __deleteJob(self, jobID): if not result["OK"]: self.log.warn("Failed to delete job from the TaskQueue") - # if it was the last job for the pilot, clear PilotsLogging about it + # if it was the last job for the pilot result = self.pilotAgentsDB.getPilotsForJobID(jobID) if not result["OK"]: self.log.error("Failed to get Pilots for JobID", result["Message"]) @@ -455,7 +443,7 @@ def __deleteJob(self, jobID): if not res["OK"]: self.log.error("Failed to get jobs for pilot", res["Message"]) return res - if not res["Value"]: # if list of jobs for pilot is empty, delete pilot and pilotslogging + if not res["Value"]: # if list of jobs for pilot is empty, delete pilot result = self.pilotAgentsDB.getPilotInfo(pilotID=pilot) if not result["OK"]: self.log.error("Failed to get pilot info", result["Message"]) @@ -465,11 +453,6 @@ def __deleteJob(self, jobID): if not ret["OK"]: self.log.error("Failed to delete pilot from PilotAgentsDB", ret["Message"]) return ret - if self.pilotsLoggingDB: - ret = self.pilotsLoggingDB.deletePilotsLogging(pilotRef) - if not ret["OK"]: - self.log.error("Failed to delete pilot logging from PilotAgentsDB", ret["Message"]) - return ret return S_OK() diff --git a/src/DIRAC/WorkloadManagementSystem/Service/PilotManagerHandler.py b/src/DIRAC/WorkloadManagementSystem/Service/PilotManagerHandler.py index 46cb769f71e..71c8d345abf 100644 --- a/src/DIRAC/WorkloadManagementSystem/Service/PilotManagerHandler.py +++ b/src/DIRAC/WorkloadManagementSystem/Service/PilotManagerHandler.py @@ -9,7 +9,6 @@ from DIRAC.Core.DISET.RequestHandler import RequestHandler from DIRAC.Core.Utilities.ObjectLoader import ObjectLoader -from DIRAC.ConfigurationSystem.Client.Helpers.Operations import Operations from DIRAC.ConfigurationSystem.Client.Helpers.Registry import getUsernameForDN, getDNForUsername from DIRAC.WorkloadManagementSystem.Client import PilotStatus from DIRAC.WorkloadManagementSystem.Service.WMSUtilities import ( @@ -34,17 +33,6 @@ def initializeHandler(cls, serviceInfoDict): except RuntimeError as excp: return S_ERROR("Can't connect to DB: %s" % excp) - cls.pilotsLoggingDB = None - enablePilotsLogging = Operations().getValue("/Services/JobMonitoring/usePilotsLoggingFlag", False) - if enablePilotsLogging: - try: - result = ObjectLoader().loadObject("WorkloadManagementSystem.DB.PilotsLoggingDB", "PilotsLoggingDB") - if not result["OK"]: - return result - cls.pilotsLoggingDB = result["Value"](parentLogger=cls.log) - except RuntimeError as excp: - return S_ERROR("Can't connect to DB: %s" % excp) - return S_OK() ############################################################################## @@ -489,17 +477,6 @@ def export_deletePilots(cls, pilotIDs): result = cls.pilotAgentsDB.deletePilots(pilotIDs) if not result["OK"]: return result - if cls.pilotsLoggingDB: - pilotIDs = result["Value"] - pilots = cls.pilotAgentsDB.getPilotInfo(pilotID=pilotIDs) - if not pilots["OK"]: - return pilots - pilotRefs = [] - for pilot in pilots: - pilotRefs.append(pilot["PilotJobReference"]) - result = cls.pilotsLoggingDB.deletePilotsLogging(pilotRefs) - if not result["OK"]: - return result return S_OK() @@ -509,19 +486,4 @@ def export_deletePilots(cls, pilotIDs): @classmethod def export_clearPilots(cls, interval=30, aborted_interval=7): - result = cls.pilotAgentsDB.clearPilots(interval, aborted_interval) - if not result["OK"]: - return result - if cls.pilotsLoggingDB: - pilotIDs = result["Value"] - pilots = cls.pilotAgentsDB.getPilotInfo(pilotID=pilotIDs) - if not pilots["OK"]: - return pilots - pilotRefs = [] - for pilot in pilots: - pilotRefs.append(pilot["PilotJobReference"]) - result = cls.pilotsLoggingDB.deletePilotsLogging(pilotRefs) - if not result["OK"]: - return result - - return S_OK() + return cls.pilotAgentsDB.clearPilots(interval, aborted_interval) diff --git a/src/DIRAC/WorkloadManagementSystem/Service/PilotsLoggingHandler.py b/src/DIRAC/WorkloadManagementSystem/Service/PilotsLoggingHandler.py deleted file mode 100644 index fde90519046..00000000000 --- a/src/DIRAC/WorkloadManagementSystem/Service/PilotsLoggingHandler.py +++ /dev/null @@ -1,102 +0,0 @@ -""" PilotsLoggingHandler is the implementation of the PilotsLogging service - - The following methods are available in the Service interface - - addPilotsLogging() - getPilotsLogging - deletePilotsLogging() - -""" -from DIRAC import S_OK, S_ERROR, gConfig -from DIRAC.Core.Utilities.ObjectLoader import ObjectLoader -from DIRAC.Core.DISET.RequestHandler import RequestHandler - -from DIRAC.Resources.MessageQueue.MQCommunication import createConsumer - - -class PilotsLoggingHandler(RequestHandler): - """Server side functions for Pilots Logging service""" - - @classmethod - def initializeHandler(cls, serviceInfoDict): - """Initialization of Pilots Logging service""" - cls.consumersSet = set() - try: - result = ObjectLoader().loadObject("WorkloadManagementSystem.DB.PilotsLoggingDB", "PilotsLoggingDB") - if not result["OK"]: - return result - cls.pilotsLoggingDB = result["Value"](parentLogger=cls.log) - - except RuntimeError as excp: - return S_ERROR("Can't connect to DB: %s" % excp) - - queue = cls.srv_getCSOption("PilotsLoggingQueue") - # This is pretty awful hack. Somehow, for uknown reason, I cannot access CS with srv_getCSOption. - # The only way is using full CS path, so I'm using it as a backup solution. - if not queue: - queue = gConfig.getValue(serviceInfoDict["serviceSectionPath"] + "/PilotsLoggingQueue") - result = createConsumer(queue, callback=cls.consumingCallback) - if result["OK"]: - cls.consumersSet.add(result["Value"]) - else: - return result - return S_OK() - - @classmethod - def consumingCallback(cls, headers, message): - """ - Callback function for the MQ Consumer, called for every new message and inserting it into database. - - :param headers: Headers of MQ message (not used) - :param message: Message represented as a dictionary - """ - # verify received message format - if set(message) == {"pilotUUID", "timestamp", "source", "phase", "status", "messageContent"}: - cls.pilotsLoggingDB.addPilotsLogging( - message["pilotUUID"], - message["timestamp"], - message["source"], - message["phase"], - message["status"], - message["messageContent"], - ) - - types_addPilotsLogging = [str, str, str, str, str, str] - - @classmethod - def export_addPilotsLogging(cls, pilotUUID, timestamp, source, phase, status, messageContent): - """ - Add new Pilots Logging entry - - :param pilotUUID: Pilot reference - :param status: Pilot status - :param minorStatus: Additional status information - :param timeStamp: Date and time of status event - :param source: Source of statu information - """ - - return cls.pilotsLoggingDB.addPilotsLogging(pilotUUID, timestamp, source, phase, status, messageContent) - - types_getPilotsLogging = [str] - - @classmethod - def export_getPilotsLogging(cls, pilotUUID): - """ - Get all Logging entries for Pilot - - :param pilotUUID: Pilot reference - """ - - return cls.pilotsLoggingDB.getPilotsLogging(pilotUUID) - - types_deletePilotsLogging = [[str, list]] - - @classmethod - def export_deletePilotsLogging(cls, pilotUUID): - """ - Delete all Logging entries for Pilot - - :param pilotUUID: Pilot reference - """ - - return cls.pilotsLoggingDB.deletePilotsLogging(pilotUUID) diff --git a/src/DIRAC/WorkloadManagementSystem/scripts/dirac_admin_pilot_logging_info.py b/src/DIRAC/WorkloadManagementSystem/scripts/dirac_admin_pilot_logging_info.py deleted file mode 100755 index 8d5524400e9..00000000000 --- a/src/DIRAC/WorkloadManagementSystem/scripts/dirac_admin_pilot_logging_info.py +++ /dev/null @@ -1,61 +0,0 @@ -#! /usr/bin/env python -""" -Get Pilots Logging for specific Pilot UUID or Job ID. - -WARNING: Only one option (either uuid or jobid) should be used. -""" -import DIRAC -from DIRAC import S_OK, gLogger -from DIRAC.Core.Base.Script import Script - -uuid = None -jobid = None - - -def setUUID(optVal): - """ - Set UUID from arguments - """ - global uuid - uuid = optVal - return S_OK() - - -def setJobID(optVal): - """ - Set JobID from arguments - """ - global jobid - jobid = optVal - return S_OK() - - -@Script() -def main(): - global uuid - global jobid - Script.registerSwitch("u:", "uuid=", "get PilotsLogging for given Pilot UUID", setUUID) - Script.registerSwitch("j:", "jobid=", "get PilotsLogging for given Job ID", setJobID) - Script.parseCommandLine() - - from DIRAC.WorkloadManagementSystem.Client.PilotManagerClient import PilotManagerClient - - if jobid: - result = PilotManagerClient().getPilots(jobid) - if not result["OK"]: - gLogger.error(result["Message"]) - DIRAC.exit(1) - gLogger.debug(result["Value"]) - uuid = list(result["Value"])[0] - - result = PilotManagerClient().getPilotLoggingInfo(uuid) - if not result["OK"]: - gLogger.error(result["Message"]) - DIRAC.exit(1) - gLogger.notice(result["Value"]) - - DIRAC.exit(0) - - -if __name__ == "__main__": - main() diff --git a/tests/Integration/WorkloadManagementSystem/Test_PilotsLoggingClient.py b/tests/Integration/WorkloadManagementSystem/Test_PilotsLoggingClient.py deleted file mode 100644 index 2f93939c334..00000000000 --- a/tests/Integration/WorkloadManagementSystem/Test_PilotsLoggingClient.py +++ /dev/null @@ -1,114 +0,0 @@ -""" This is a test of the chain - PilotsLoggingClient -> PilotsLoggingHandler -> PilotsLoggingDB - - It supposes that the DB is present, and that the service is running -""" -import unittest -import sys - -from DIRAC.WorkloadManagementSystem.Client.PilotsLoggingClient import PilotsLoggingClient - -import DIRAC - -DIRAC.initialize() # Initialize configuration - - -class TestPilotsLogging(unittest.TestCase): - def setUp(self): - self.pilotsLoggingClient = PilotsLoggingClient() - - def tearDown(self): - pass - - -class PilotsLogging(TestPilotsLogging): - def test_PilotsLoggingAddGetDelete(self): - resp = self.pilotsLoggingClient.addPilotsLogging( - "11111111-1111-1111-1111-111111111111", "timestamp1", "test", "phase", "status", "messageContent" - ) - self.assertTrue(resp["OK"], "Failed to add PilotsLogging") - resp = self.pilotsLoggingClient.addPilotsLogging( - "11111111-1111-1111-1111-111111111111", "timestamp2", "test2", "phase2", "status2", "messageContent2" - ) - self.assertTrue(resp["OK"], "Failed to add PilotsLogging") - resp = self.pilotsLoggingClient.getPilotsLogging("11111111-1111-1111-1111-111111111111") - self.assertTrue(resp["OK"], "Failed to get PilotsLogging") - test_sample = { - "pilotUUID": "11111111-1111-1111-1111-111111111111", - "timestamp": "timestamp1", - "source": "test", - "phase": "phase", - "status": "status", - "messageContent": "messageContent", - } - test_sample2 = { - "pilotUUID": "11111111-1111-1111-1111-111111111111", - "timestamp": "timestamp2", - "source": "test2", - "phase": "phase2", - "status": "status2", - "messageContent": "messageContent2", - } - self.assertEqual(resp["Value"], [test_sample, test_sample2], "Wrong data comes out of Service") - resp = self.pilotsLoggingClient.deletePilotsLogging("11111111-1111-1111-1111-111111111111") - self.assertTrue(resp["OK"], "Failed to delete PilotsLogging") - resp = self.pilotsLoggingClient.getPilotsLogging("11111111-1111-1111-1111-111111111111") - self.assertTrue(resp["OK"]) - self.assertEqual(resp["Value"], [], "PilotsLogging was not really deleted") - - def test_PilotsLoggingEmptyGetDelete(self): - - resp = self.pilotsLoggingClient.getPilotsLogging("11111111-1111-1111-1111-111111111111") - self.assertTrue(resp["OK"], "Failed to get PilotsLogging") - resp = self.pilotsLoggingClient.deletePilotsLogging("11111111-1111-1111-1111-111111111111") - self.assertTrue(resp["OK"], "Failed to delete PilotsLogging") - - def test_PilotsLoggingDeleteList(self): - - test_sample1 = { - "pilotUUID": "11111111-1111-1111-1111-111111111111", - "timestamp": "timestamp1", - "source": "test", - "phase": "phase1", - "status": "status1", - "messageContent": "messageContent1", - } - test_sample2 = { - "pilotUUID": "22222222-2222-2222-2222-222222222222", - "timestamp": "timestamp2", - "source": "test", - "phase": "phase2", - "status": "status2", - "messageContent": "messageContent2", - } - - resp = self.pilotsLoggingClient.addPilotsLogging( - "11111111-1111-1111-1111-111111111111", "timestamp1", "test", "phase1", "status1", "messageContent1" - ) - self.assertTrue(resp["OK"], "Failed to add PilotsLogging") - resp = self.pilotsLoggingClient.addPilotsLogging( - "22222222-2222-2222-2222-222222222222", "timestamp2", "test", "phase2", "status2", "messageContent2" - ) - resp = self.pilotsLoggingClient.getPilotsLogging("11111111-1111-1111-1111-111111111111") - self.assertTrue(resp["OK"], "Failed to get PilotsLogging") - self.assertEqual(resp["Value"], [test_sample1], "Wrong data comes out of Service") - resp = self.pilotsLoggingClient.getPilotsLogging("22222222-2222-2222-2222-222222222222") - self.assertTrue(resp["OK"], "Failed to get PilotsLogging") - self.assertEqual(resp["Value"], [test_sample2], "Wrong data comes out of Service") - resp = self.pilotsLoggingClient.deletePilotsLogging( - ["11111111-1111-1111-1111-111111111111", "22222222-2222-2222-2222-222222222222"] - ) - self.assertTrue(resp["OK"], "Failed to delete PilotsLogging") - resp = self.pilotsLoggingClient.getPilotsLogging("11111111-1111-1111-1111-111111111111") - self.assertTrue(resp["OK"], "Failed to get PilotsLogging") - self.assertEqual(resp["Value"], [], "PilotsLogging was not really deleted") - resp = self.pilotsLoggingClient.getPilotsLogging("22222222-2222-2222-2222-222222222222") - self.assertTrue(resp["OK"], "Failed to get PilotsLogging") - self.assertEqual(resp["Value"], [], "PilotsLogging was not really deleted") - - -if __name__ == "__main__": - suite = unittest.defaultTestLoader.loadTestsFromTestCase(TestPilotsLogging) - suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(PilotsLogging)) - testResult = unittest.TextTestRunner(verbosity=2).run(suite) - sys.exit(not testResult.wasSuccessful()) diff --git a/tests/Integration/all_integration_client_tests.sh b/tests/Integration/all_integration_client_tests.sh index 75208cbf706..8678d93aec3 100644 --- a/tests/Integration/all_integration_client_tests.sh +++ b/tests/Integration/all_integration_client_tests.sh @@ -52,7 +52,6 @@ pytest --no-check-dirac-environment "${THIS_DIR}/ResourceStatusSystem/Test_Email #-------------------------------------------------------------------------------# echo -e "*** $(date -u) **** WMS TESTS ****\n" -# pytest --no-check-dirac-environment "${CLIENTINSTALLDIR}"/DIRAC/tests/Integration/WorkloadManagementSystem/Test_PilotsLoggingClient.py |& tee -a clientTestOutputs.txt pytest --no-check-dirac-environment "${THIS_DIR}/WorkloadManagementSystem/Test_SandboxStoreClient.py" |& tee -a clientTestOutputs.txt; (( ERR |= "${?}" )) pytest --no-check-dirac-environment "${THIS_DIR}/WorkloadManagementSystem/Test_JobWrapper.py" |& tee -a clientTestOutputs.txt; (( ERR |= "${?}" )) pytest --no-check-dirac-environment "${THIS_DIR}/WorkloadManagementSystem/Test_PilotsClient.py" |& tee -a clientTestOutputs.txt; (( ERR |= "${?}" )) diff --git a/tests/Jenkins/utilities.sh b/tests/Jenkins/utilities.sh index ebbd9916b8a..a3170518457 100644 --- a/tests/Jenkins/utilities.sh +++ b/tests/Jenkins/utilities.sh @@ -662,7 +662,7 @@ diracServices(){ echo '==> [diracServices]' # Ignore tornado services - local services=$(cut -d '.' -f 1 < services | grep -v Tornado | grep -v TokenManager | grep -v PilotsLogging | grep -v StorageElementHandler | grep -v ^ConfigurationSystem | grep -v RAWIntegrity | grep -v RunDBInterface | grep -v ComponentMonitoring | sed 's/System / /g' | sed 's/Handler//g' | sed 's/ /\//g') + local services=$(cut -d '.' -f 1 < services | grep -v Tornado | grep -v TokenManager | grep -v StorageElementHandler | grep -v ^ConfigurationSystem | grep -v RAWIntegrity | grep -v RunDBInterface | grep -v ComponentMonitoring | sed 's/System / /g' | sed 's/Handler//g' | sed 's/ /\//g') # group proxy, will be uploaded explicitly # echo '==> getting/uploading proxy for prod'