diff --git a/.github/workflows/basic.yml b/.github/workflows/basic.yml index e35f05479ed..e5cde5802ec 100644 --- a/.github/workflows/basic.yml +++ b/.github/workflows/basic.yml @@ -12,11 +12,11 @@ jobs: fail-fast: False matrix: command: - - pytest - - DIRAC_USE_M2CRYPTO=Yes DIRAC_M2CRYPTO_SPLIT_HANDSHAKE=Yes pytest + - pytest --no-cov + - DIRAC_USE_M2CRYPTO=Yes DIRAC_M2CRYPTO_SPLIT_HANDSHAKE=Yes pytest --no-cov # Security tests are flakey due to reference counting bugs in pyGSI/src/crypto/asn1.c - - pytest Core/Security/test || (echo "Retrying..."; pytest Core/Security/test) || (echo "Retrying again..."; pytest Core/Security/test) - - DIRAC_USE_M2CRYPTO=Yes DIRAC_M2CRYPTO_SPLIT_HANDSHAKE=Yes pytest Core/Security/test || (echo "Retrying..."; DIRAC_USE_M2CRYPTO=No pytest Core/Security/test) || (echo "Retrying again..."; DIRAC_USE_M2CRYPTO=No pytest Core/Security/test) + - pytest --no-cov Core/Security/test + - DIRAC_USE_M2CRYPTO=Yes DIRAC_M2CRYPTO_SPLIT_HANDSHAKE=Yes pytest --no-cov Core/Security/test - tests/checkDocs.sh # TODO This should cover more than just tests/CI # Excluded codes related to sourcing files diff --git a/.gitignore b/.gitignore index a6d1f26792f..85f8efb537c 100644 --- a/.gitignore +++ b/.gitignore @@ -73,6 +73,7 @@ tests/CI/SERVERCONFIG # VSCode .vscode +.env # docs # this is auto generated diff --git a/ConfigurationSystem/Client/CSAPI.py b/ConfigurationSystem/Client/CSAPI.py index 2a8f1b1b0e5..2f24fb2fe56 100644 --- a/ConfigurationSystem/Client/CSAPI.py +++ b/ConfigurationSystem/Client/CSAPI.py @@ -210,7 +210,7 @@ def listHosts(self): def describeUsers(self, users=None): """ describe users by nickname - :param list users: list of users' nickanames + :param list users: list of users' nicknames :return: a S_OK(description) of the users in input """ if users is None: @@ -329,14 +329,14 @@ def addUser(self, username, properties): """ Add a user to the cs - :param str username: group name - :param dict properties: dictionary describing user properties: + :param str username: username + :param dict properties: dictionary describing user properties: - - DN - - groups - - + - DN + - groups + - - :return: True/False + :return: True/False """ if not self.__initialized['OK']: return self.__initialized @@ -368,14 +368,15 @@ def modifyUser(self, username, properties, createIfNonExistant=False): """ Modify a user - :param str username: group name - :param dict properties: dictionary describing user properties: + :param str username: group name + :param dict properties: dictionary describing user properties: - DN - Groups - - :return: S_OK, Value = True/False + :param bool createIfNonExistant: if true, registers the users if it did not exist + :return: S_OK, Value = True/False """ if not self.__initialized['OK']: return self.__initialized @@ -430,14 +431,14 @@ def addGroup(self, groupname, properties): """ Add a group to the cs - :param str groupname: group name - :param dict properties: dictionary describing group properties: + :param str groupname: group name + :param dict properties: dictionary describing group properties: - Users - Properties - - :return: True/False + :return: S_OK, Value = True/False """ if not self.__initialized['OK']: return self.__initialized @@ -455,14 +456,15 @@ def modifyGroup(self, groupname, properties, createIfNonExistant=False): """ Modify a group - :param str groupname: group name - :param dict properties: dictionary describing group properties: + :param str groupname: group name + :param dict properties: dictionary describing group properties: - Users - Properties - - :return: True/False + :param bool createIfNonExistant: if true, creates the group if it did not exist + :return: S_OK, Value = True/False """ if not self.__initialized['OK']: return self.__initialized @@ -490,14 +492,15 @@ def modifyGroup(self, groupname, properties, createIfNonExistant=False): def addHost(self, hostname, properties): """ Add a host to the cs - :param str hostname: hostname name - :param dict properties: dictionary describing host properties: + + :param str hostname: host name + :param dict properties: dictionary describing host properties: - DN - Properties - - :return: True/False + :return: S_OK, Value = True/False """ if not self.__initialized['OK']: return self.__initialized @@ -617,14 +620,16 @@ def getOpsSection(): def modifyHost(self, hostname, properties, createIfNonExistant=False): """ Modify a host - :param str hostname: hostname name - :param dict properties: dictionary describing host properties: + + :param str hostname: hostname name + :param dict properties: dictionary describing host properties: - DN - Properties - - :return: True/False + :param bool createIfNonExistant: if true, creates the host if it did not exist + :return: S_OK, Value = True/False """ if not self.__initialized['OK']: return self.__initialized diff --git a/ConfigurationSystem/Client/Config.py b/ConfigurationSystem/Client/Config.py index 11314f2a160..6363f77d4c5 100755 --- a/ConfigurationSystem/Client/Config.py +++ b/ConfigurationSystem/Client/Config.py @@ -6,8 +6,13 @@ from DIRAC.ConfigurationSystem.private.ConfigurationClient import ConfigurationClient +#: Global gConfig object of type :class:`~DIRAC.ConfigurationSystem.private.ConfigurationClient.ConfigurationClient` gConfig = ConfigurationClient() def getConfig(): + """ + :returns: gConfig + :rtype: ~DIRAC.ConfigurationSystem.private.ConfigurationClient.ConfigurationClient + """ return gConfig diff --git a/Core/DISET/private/Transports/M2SSLTransport.py b/Core/DISET/private/Transports/M2SSLTransport.py index 9def1d7fd63..fde34b9fbb3 100755 --- a/Core/DISET/private/Transports/M2SSLTransport.py +++ b/Core/DISET/private/Transports/M2SSLTransport.py @@ -45,6 +45,9 @@ def __init__(self, *args, **kwargs): as for other transports. If ctx is specified (as an instance of SSL.Context) then use that rather than creating a new context. """ + # The thread init of M2Crypto is not really thread safe. + # So we put it a second time + M2Threading.init() self.remoteAddress = None self.peerCredentials = {} self.__timeout = 1 @@ -98,9 +101,7 @@ def initAsClient(self): error = "%s:%s" % (e, repr(e)) if self.oSocket is not None: - self.oSocket.close() - self.oSocket.socket.close() - self.oSocket = None + self.close() return S_ERROR(error) @@ -125,20 +126,85 @@ def initAsServer(self): return S_OK() def close(self): + # pylint: disable=line-too-long """ Close this socket. """ + if self.oSocket: + # TL;DR: + # Do NOT touch that method + # # Surprisingly (to me at least), M2Crypto does not close - # the socket when calling SSL.Connection.close - # It only does it when the garbage collector kicks in - # We have to manually close it here, otherwise the connections - # will hang forever + # the underlying socket when calling SSL.Connection.close + # It only does it when the garbage collector kicks in (see ~M2Crypto.SSL.Connection.Connection.__del__) + # If the socket is not closed, the connection may hang forever. + # + # Thus, we are setting self.oSocket to None to allow the GC to do the work, but since we are not sure + # that it will run, we anyway force the connection to be closed + # + # However, we should close the underlying socket only after SSL was shutdown properly. + # This is because OpenSSL `ssl3_shutdown` (see callstack below) may still read some data + # (see https://github.com/openssl/openssl/blob/master/ssl/s3_lib.c#L4509):: + # + # + # 1 0x00007fffe9d48fc0 in sock_read () from /lib/libcrypto.so.1.0.0 + # 2 0x00007fffe9d46e83 in BIO_read () from /lib/libcrypto.so.1.0.0 + # 3 0x00007fffe9eab9dd in ssl3_read_n () from /lib/libssl.so.1.0.0 + # 4 0x00007fffe9ead216 in ssl3_read_bytes () from /lib/libssl.so.1.0.0 + # 5 0x00007fffe9ea999c in ssl3_shutdown () from /lib/libssl.so.1.0.0 + # 6 0x00007fffe9ed4f93 in ssl_free () from /lib/libssl.so.1.0.0 + # 7 0x00007fffe9d46d5b in BIO_free () from /lib/libcrypto.so.1.0.0 + # 8 0x00007fffe9f30a96 in bio_free (bio=0x5555556f3200) at SWIG/_m2crypto_wrap.c:5008 + # 9 0x00007fffe9f30b1e in _wrap_bio_free (self=, args=) at SWIG/_m2crypto_wrap.c + # + # We unfortunately have no way to force that order, and there is a risk of deadlock + # when running in a multi threaded environment like the agents:: + # + # Thread A opens socket, gets FD = 111 + # Thread A works on it + # Thread A closes FD 111 (underlying socket.close()) + # Thread B opens socket, gets FD = 111 + # Thread A calls read on FD=111 from ssl3_shutdown + # + # This is illustrated on the strace below:: + # + # 26461 14:25:15.266692 write(111]:42688->[]:9140]>, + # "blabla", 37 + # 26464 14:25:15.266857 <... connect resumed>) = 0 <0.000195> + # 26464 14:25:15.267023 getsockname(120:44252->188.185.84.86:9140]>, + # 26461 14:25:15.267176 <... write resumed>) = 37 <0.000453> + # 26464 14:25:15.267425 <... getsockname resumed>{sa_family=AF_INET, sin_port=htons(44252), + # sin_addr=inet_addr("")}, [28->16]) = 0 <0.000292> + # 26461 14:25:15.267466 close(111]:42688->[]:9140]> + # 26464 14:25:15.267637 close(120:44252->188.185.84.86:9140]> + # 26464 14:25:15.267738 <... close resumed>) = 0 <0.000086> + # 26461 14:25:15.267768 <... close resumed>) = 0 <0.000285> + # 26464 14:25:15.267827 socket(AF_INET6, SOCK_DGRAM|SOCK_CLOEXEC, IPPROTO_IP + # 26461 14:25:15.267888 futex(0x21f8620, FUTEX_WAKE_PRIVATE, 1 + # 26464 14:25:15.267976 <... socket resumed>) = 111 <0.000138> + # 26461 14:25:15.268092 <... futex resumed>) = 1 <0.000196> + # 26464 14:25:15.268195 connect(111, + # {sa_family=AF_INET6, sin6_port=htons(9140), + # inet_pton(AF_INET6, "", &sin6_addr), + # sin6_flowinfo=htonl(0), sin6_scope_id=0 + # }, 28 + # 26461 14:25:15.268294 read(111]:42480->[]:9140]>, + # 26464 14:25:15.268503 <... connect resumed>) = 0 <0.000217> + # 26464 14:25:15.268673 getsockname(111]:42480->[]:9140]>, + # 26464 14:25:15.268862 <... getsockname resumed>{sa_family=AF_INET6, sin6_port=htons(42480), + # inet_pton(AF_INET6, "", &sin6_addr), sin6_flowinfo=htonl(0), sin6_scope_id= + # 0}, [28]) = 0 <0.000168> + # 26464 14:25:15.269048 + # close(111]:42480->[]:9140]> + # + self.oSocket.close() - self.oSocket.socket.close() - # del self.oSocket + underlyingSocket = self.oSocket.socket self.oSocket = None + underlyingSocket.close() return S_OK() def renewServerContext(self): + # pylint: disable=line-too-long """ Renews the server context. This reloads the certificates and re-initialises the SSL context. diff --git a/DataManagementSystem/Agent/FTS3Agent.py b/DataManagementSystem/Agent/FTS3Agent.py index 0a88c777fb1..b1ec43d52e2 100644 --- a/DataManagementSystem/Agent/FTS3Agent.py +++ b/DataManagementSystem/Agent/FTS3Agent.py @@ -369,24 +369,36 @@ def _treatOperation(self, operation): continueOperationProcessing = True # Check the status of the associated RMS Request. - # If it is canceled then we will not create new FTS3Jobs, and mark + # If it is canceled or does not exist anymore then we will not create new FTS3Jobs, and mark # this as FTS3Operation canceled. if operation.rmsReqID: res = ReqClient().getRequestStatus(operation.rmsReqID) if not res['OK']: - log.error("Could not get request status", res) - return operation, res - rmsReqStatus = res['Value'] - - if rmsReqStatus == 'Canceled': - log.info( - "The RMS Request is canceled, canceling the FTS3Operation", - "rmsReqID: %s, FTS3OperationID: %s" % - (operation.rmsReqID, - operation.operationID)) - operation.status = 'Canceled' - continueOperationProcessing = False + # If the Request does not exist anymore + if cmpError(res, errno.ENOENT): + log.info( + "The RMS Request does not exist anymore, canceling the FTS3Operation", + "rmsReqID: %s, FTS3OperationID: %s" % + (operation.rmsReqID, + operation.operationID)) + operation.status = 'Canceled' + continueOperationProcessing = False + else: + log.error("Could not get request status", res) + return operation, res + + else: + rmsReqStatus = res['Value'] + + if rmsReqStatus == 'Canceled': + log.info( + "The RMS Request is canceled, canceling the FTS3Operation", + "rmsReqID: %s, FTS3OperationID: %s" % + (operation.rmsReqID, + operation.operationID)) + operation.status = 'Canceled' + continueOperationProcessing = False if continueOperationProcessing: res = operation.prepareNewJobs( diff --git a/DataManagementSystem/scripts/dirac-dms-add-file.py b/DataManagementSystem/scripts/dirac-dms-add-file.py index bb5bf2c59b6..d3ffc0eea9d 100755 --- a/DataManagementSystem/scripts/dirac-dms-add-file.py +++ b/DataManagementSystem/scripts/dirac-dms-add-file.py @@ -61,6 +61,11 @@ def getDict(item_list): return lfn_dict +from DIRAC.DataManagementSystem.Client.DataManager import DataManager +from DIRAC import gLogger +import DIRAC +exitCode = 0 + lfns = [] if len(args) == 1: inputFileName = args[0] @@ -72,14 +77,12 @@ def getDict(item_list): items[0] = items[0].replace('LFN:', '').replace('lfn:', '') lfns.append(getDict(items)) inputFile.close() + else: + gLogger.error("Error: LFN list '%s' missing." % inputFileName) + exitCode = 4 else: lfns.append(getDict(args)) -from DIRAC.DataManagementSystem.Client.DataManager import DataManager -from DIRAC import gLogger -import DIRAC -exitCode = 0 - dm = DataManager() for lfn in lfns: if not os.path.exists(lfn['localfile']): diff --git a/FrameworkSystem/Client/ComponentInstaller.py b/FrameworkSystem/Client/ComponentInstaller.py index 51371631e40..cf60348151e 100644 --- a/FrameworkSystem/Client/ComponentInstaller.py +++ b/FrameworkSystem/Client/ComponentInstaller.py @@ -1132,7 +1132,7 @@ def getSetupComponents(self): for cType in self.componentTypes: if body.find('dirac-%s' % (cType)) != -1: - system, compT = component.split('_')[0:2] + system, compT = component.split('_', 1) if system not in resultDict[resultIndexes[cType]]: resultDict[resultIndexes[cType]][system] = [] resultDict[resultIndexes[cType]][system].append(compT) diff --git a/Interfaces/scripts/dirac-admin-add-host.py b/Interfaces/scripts/dirac-admin-add-host.py index 2a96fc4d4e6..215edba3e9c 100755 --- a/Interfaces/scripts/dirac-admin-add-host.py +++ b/Interfaces/scripts/dirac-admin-add-host.py @@ -85,6 +85,19 @@ def addProperty(arg): errorList.append(("commit", result['Message'])) exitCode = 255 +if exitCode == 0: + from DIRAC.FrameworkSystem.Client.ComponentMonitoringClient import ComponentMonitoringClient + cmc = ComponentMonitoringClient() + ret = cmc.hostExists(dict(HostName=hostName)) + if not ret['OK']: + Script.gLogger.error('Cannot check if host is registered in ComponentMonitoring', ret['Message']) + elif ret['Value']: + Script.gLogger.info('Host already registered in ComponentMonitoring') + else: + ret = cmc.addHost(dict(HostName=hostName, CPU='TO_COME')) + if not ret['OK']: + Script.gLogger.error('Failed to add Host to ComponentMonitoring', ret['Message']) + for error in errorList: Script.gLogger.error("%s: %s" % error) diff --git a/RequestManagementSystem/DB/RequestDB.py b/RequestManagementSystem/DB/RequestDB.py index 8da61f99f25..a1e1acaf70b 100644 --- a/RequestManagementSystem/DB/RequestDB.py +++ b/RequestManagementSystem/DB/RequestDB.py @@ -17,6 +17,7 @@ db holding Request, Operation and File """ import six +import errno import random import datetime @@ -666,7 +667,7 @@ def getRequestCountersWeb(self, groupingAttribute, selectDict): else: summaryQuery = summaryQuery.filter(eval('%s.%s' % (objectType, key)) == value) - summaryQuery = summaryQuery.group_by(groupingAttribute) + summaryQuery = summaryQuery.group_by(eval(groupingAttribute)) try: requestLists = summaryQuery.all() @@ -782,7 +783,7 @@ def getRequestStatus(self, requestID): try: status = session.query(Request._Status).filter(Request.RequestID == requestID).one() except NoResultFound: - return S_ERROR("Request %s does not exist" % requestID) + return S_ERROR(errno.ENOENT, "Request %s does not exist" % requestID) finally: session.close() return S_OK(status[0]) diff --git a/TransformationSystem/Client/TaskManager.py b/TransformationSystem/Client/TaskManager.py index c028a6e7dbe..89ea9ed31f0 100644 --- a/TransformationSystem/Client/TaskManager.py +++ b/TransformationSystem/Client/TaskManager.py @@ -828,8 +828,8 @@ def _checkSickTransformations(self, transID): """ Check if the transformation is in the transformations to be processed at Hospital or Clinic """ transID = int(transID) - clinicPath = "Hospital/Transformations" - if transID in set(int(x) for x in self.opsH.getValue(clinicPath, [])): + clinicPath = "Hospital" + if transID in set(int(x) for x in self.opsH.getValue(os.path.join(clinicPath, "Transformations"), [])): return clinicPath if "Clinics" in self.opsH.getSections("Hospital").get('Value', []): basePath = os.path.join("Hospital", "Clinics") diff --git a/TransformationSystem/scripts/dirac-transformation-archive.py b/TransformationSystem/scripts/dirac-transformation-archive.py index 21c030ef086..3577bb39c27 100755 --- a/TransformationSystem/scripts/dirac-transformation-archive.py +++ b/TransformationSystem/scripts/dirac-transformation-archive.py @@ -5,14 +5,14 @@ from __future__ import print_function import sys -from DIRAC.Core.Base.Script import parseCommandLine +from DIRAC.Core.Base.Script import parseCommandLine, getPositionalArgs parseCommandLine() -if len(sys.argv) < 2: +if not getPositionalArgs(): print('Usage: dirac-transformation-archive transID [transID] [transID]') sys.exit() else: - transIDs = [int(arg) for arg in sys.argv[1:]] + transIDs = [int(arg) for arg in getPositionalArgs()] from DIRAC.TransformationSystem.Agent.TransformationCleaningAgent import TransformationCleaningAgent from DIRAC.TransformationSystem.Client.TransformationClient import TransformationClient diff --git a/TransformationSystem/scripts/dirac-transformation-clean.py b/TransformationSystem/scripts/dirac-transformation-clean.py index 0e07e23120d..8d808869e69 100755 --- a/TransformationSystem/scripts/dirac-transformation-clean.py +++ b/TransformationSystem/scripts/dirac-transformation-clean.py @@ -5,18 +5,17 @@ from __future__ import print_function import sys -from DIRAC.Core.Base.Script import parseCommandLine +from DIRAC.Core.Base.Script import parseCommandLine, getPositionalArgs parseCommandLine() from DIRAC.TransformationSystem.Agent.TransformationCleaningAgent import TransformationCleaningAgent from DIRAC.TransformationSystem.Client.TransformationClient import TransformationClient -if len(sys.argv) < 2: +if not getPositionalArgs(): print('Usage: dirac-transformation-clean transID [transID] [transID]') sys.exit() else: - transIDs = [int(arg) for arg in sys.argv[1:]] - + transIDs = [int(arg) for arg in getPositionalArgs()] agent = TransformationCleaningAgent('Transformation/TransformationCleaningAgent', 'Transformation/TransformationCleaningAgent', diff --git a/TransformationSystem/scripts/dirac-transformation-remove-output.py b/TransformationSystem/scripts/dirac-transformation-remove-output.py index a67f9ba33ae..31744773638 100755 --- a/TransformationSystem/scripts/dirac-transformation-remove-output.py +++ b/TransformationSystem/scripts/dirac-transformation-remove-output.py @@ -5,14 +5,14 @@ from __future__ import print_function import sys -from DIRAC.Core.Base.Script import parseCommandLine +from DIRAC.Core.Base.Script import parseCommandLine, getPositionalArgs parseCommandLine() -if len(sys.argv) < 2: +if not getPositionalArgs(): print('Usage: dirac-transformation-remove-output transID [transID] [transID]') sys.exit() else: - transIDs = [int(arg) for arg in sys.argv[1:]] + transIDs = [int(arg) for arg in getPositionalArgs()] from DIRAC.TransformationSystem.Agent.TransformationCleaningAgent import TransformationCleaningAgent from DIRAC.TransformationSystem.Client.TransformationClient import TransformationClient diff --git a/TransformationSystem/scripts/dirac-transformation-verify-outputdata.py b/TransformationSystem/scripts/dirac-transformation-verify-outputdata.py index 0202e77bdb1..1476d11eb4a 100755 --- a/TransformationSystem/scripts/dirac-transformation-verify-outputdata.py +++ b/TransformationSystem/scripts/dirac-transformation-verify-outputdata.py @@ -5,14 +5,14 @@ from __future__ import print_function import sys -from DIRAC.Core.Base.Script import parseCommandLine +from DIRAC.Core.Base.Script import parseCommandLine, getPositionalArgs parseCommandLine() -if len(sys.argv) < 2: +if not getPositionalArgs(): print('Usage: dirac-transformation-verify-outputdata transID [transID] [transID]') sys.exit() else: - transIDs = [int(arg) for arg in sys.argv[1:]] + transIDs = [int(arg) for arg in getPositionalArgs()] from DIRAC.TransformationSystem.Agent.ValidateOutputDataAgent import ValidateOutputDataAgent from DIRAC.TransformationSystem.Client.TransformationClient import TransformationClient diff --git a/WorkloadManagementSystem/PilotAgent/test/Test_Pilot.py b/WorkloadManagementSystem/PilotAgent/test/Test_Pilot.py index 8e986940958..a183bf9f959 100644 --- a/WorkloadManagementSystem/PilotAgent/test/Test_Pilot.py +++ b/WorkloadManagementSystem/PilotAgent/test/Test_Pilot.py @@ -2,54 +2,33 @@ """ # imports -import unittest import json import os +import sys + +if "--no-cov" in sys.argv: + del sys.argv[sys.argv.index('--no-cov')] from DIRAC.WorkloadManagementSystem.PilotAgent.pilotTools import PilotParams, CommandBase from DIRAC.WorkloadManagementSystem.PilotAgent.pilotCommands import GetPilotVersion -class PilotTestCase( unittest.TestCase ): - """ Base class for the Agents test cases - """ - def setUp( self ): - self.pp = PilotParams() - - def tearDown( self ): - try: - os.remove('pilot.out') - os.remove( 'pilot.json' ) - os.remove( 'pilot.json-local' ) - except OSError: - pass - - -class CommandsTestCase( PilotTestCase ): - - def test_commandBase(self): - cb = CommandBase(self.pp) - returnCode, _outputData = cb.executeAndGetOutput("ls") - self.assertEqual(returnCode, 0) - - def test_GetPilotVersion( self ): - - # Now defining a local file for test, and all the necessary parameters - fp = open( 'pilot.json', 'w' ) - json.dump( {'TestSetup':{'Version':['v1r1', 'v2r2']}}, fp ) - fp.close() - self.pp.setup = 'TestSetup' - self.pp.pilotCFGFileLocation = 'file://%s' % os.getcwd() - gpv = GetPilotVersion( self.pp ) - self.assertIsNone( gpv.execute() ) - self.assertEqual( gpv.pp.releaseVersion, 'v1r1' ) - -############################################################################# -# Test Suite run -############################################################################# - -if __name__ == '__main__': - suite = unittest.defaultTestLoader.loadTestsFromTestCase( PilotTestCase ) - suite.addTest( unittest.defaultTestLoader.loadTestsFromTestCase( CommandsTestCase ) ) - testResult = unittest.TextTestRunner( verbosity = 2 ).run( suite ) -# EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF#EOF# +def test_GetPilotVersion(): + pp = PilotParams() + # Now defining a local file for test, and all the necessary parameters + fp = open('pilot.json', 'w') + json.dump({'TestSetup': {'Version': ['v1r1', 'v2r2']}}, fp) + fp.close() + pp.setup = 'TestSetup' + pp.pilotCFGFileLocation = 'file://%s' % os.getcwd() + gpv = GetPilotVersion(pp) + result = gpv.execute() + assert result is None + assert gpv.pp.releaseVersion == 'v1r1' + + +def test_commandBase(): + pp = PilotParams() + cb = CommandBase(pp) + returnCode, _outputData = cb.executeAndGetOutput("ls") + assert returnCode == 0 diff --git a/pytest.ini b/pytest.ini index 74fb4a3f68c..81dd39cb17f 100644 --- a/pytest.ini +++ b/pytest.ini @@ -5,4 +5,4 @@ python_files=Test_*.py assert*.py # The reason here is that we do nasty things with the pythonpath # in order to make sure that M2Crypto and pyGSI do not step # on each other's feet -addopts = -rx -v --color=yes --showlocals --tb=long --ignore=tests --ignore=Core/Security/test --cov=. --cov-report term-missing +addopts = -rx -v --color=yes --showlocals --tb=long --ignore=tests --ignore=Core/Security/test diff --git a/release.notes b/release.notes index ad16ff7b14b..6564bb704da 100644 --- a/release.notes +++ b/release.notes @@ -147,6 +147,26 @@ FIX: (#4551) align ProxyDB test to current changes NEW: (#4289) Document how to run integration tests in docker NEW: (#4551) add DNProperties description to Registry/Users subsection +[v7r0p28] + +*Core +FIX: (#4642) M2Crypto closes the socket after dereferencing the Connection instance + +*DMS +FIX: (#4644) Cancel FTS3 Operation if the RMS request does not exist + +*RMS +NEW: (#4644) getRequestStatus returns ENOENT if the request does not exist + +*TS +FIX: (#4647) TaskManager - hospital sites were not looked for correctly, which + generated an exception +FIX: (#4656) fix parsing of command line flags (e.g., -ddd) for dirac-transformation-archive/clean/remove-output/verify-outputdata + +*Interfaces +CHANGE: (#4652) dirac-admin-add-host now inserts hosts into the ComponentMonitoring if the host + is not yet known + [v7r0p27] *Framework @@ -640,6 +660,14 @@ NEW: (#4170) added Production system documentation CHANGE: (#4224) Pilot 3 is the default NEW: (#4244) Added a few notes on using the JobParameters on ElasticSearch database +[v6r22p31] + +*DMS +FIX: (#4646) Print error from dirac-dms-add-file if input LFN list file is missing. + +*RMS +FIX: (#4657) RequestDB - fix getRequestCountersWeb error in DIRACOS + [v6r22p30] *DataManagementSystem