wmglobalqueue 2.3.10__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of wmglobalqueue might be problematic. Click here for more details.
- Utils/CPMetrics.py +270 -0
- Utils/CertTools.py +62 -0
- Utils/EmailAlert.py +50 -0
- Utils/ExtendedUnitTestCase.py +62 -0
- Utils/FileTools.py +182 -0
- Utils/IteratorTools.py +80 -0
- Utils/MathUtils.py +31 -0
- Utils/MemoryCache.py +119 -0
- Utils/Patterns.py +24 -0
- Utils/Pipeline.py +137 -0
- Utils/PortForward.py +97 -0
- Utils/ProcessStats.py +103 -0
- Utils/PythonVersion.py +17 -0
- Utils/Signals.py +36 -0
- Utils/TemporaryEnvironment.py +27 -0
- Utils/Throttled.py +227 -0
- Utils/Timers.py +130 -0
- Utils/Timestamps.py +86 -0
- Utils/TokenManager.py +143 -0
- Utils/Tracing.py +60 -0
- Utils/TwPrint.py +98 -0
- Utils/Utilities.py +308 -0
- Utils/__init__.py +11 -0
- WMCore/ACDC/Collection.py +57 -0
- WMCore/ACDC/CollectionTypes.py +12 -0
- WMCore/ACDC/CouchCollection.py +67 -0
- WMCore/ACDC/CouchFileset.py +238 -0
- WMCore/ACDC/CouchService.py +73 -0
- WMCore/ACDC/DataCollectionService.py +485 -0
- WMCore/ACDC/Fileset.py +94 -0
- WMCore/ACDC/__init__.py +11 -0
- WMCore/Algorithms/Alarm.py +39 -0
- WMCore/Algorithms/MathAlgos.py +274 -0
- WMCore/Algorithms/MiscAlgos.py +67 -0
- WMCore/Algorithms/ParseXMLFile.py +115 -0
- WMCore/Algorithms/Permissions.py +27 -0
- WMCore/Algorithms/Singleton.py +58 -0
- WMCore/Algorithms/SubprocessAlgos.py +129 -0
- WMCore/Algorithms/__init__.py +7 -0
- WMCore/Cache/GenericDataCache.py +98 -0
- WMCore/Cache/WMConfigCache.py +572 -0
- WMCore/Cache/__init__.py +0 -0
- WMCore/Configuration.py +651 -0
- WMCore/DAOFactory.py +47 -0
- WMCore/DataStructs/File.py +177 -0
- WMCore/DataStructs/Fileset.py +140 -0
- WMCore/DataStructs/Job.py +182 -0
- WMCore/DataStructs/JobGroup.py +142 -0
- WMCore/DataStructs/JobPackage.py +49 -0
- WMCore/DataStructs/LumiList.py +734 -0
- WMCore/DataStructs/Mask.py +219 -0
- WMCore/DataStructs/MathStructs/ContinuousSummaryHistogram.py +197 -0
- WMCore/DataStructs/MathStructs/DiscreteSummaryHistogram.py +92 -0
- WMCore/DataStructs/MathStructs/SummaryHistogram.py +117 -0
- WMCore/DataStructs/MathStructs/__init__.py +0 -0
- WMCore/DataStructs/Pickleable.py +24 -0
- WMCore/DataStructs/Run.py +256 -0
- WMCore/DataStructs/Subscription.py +175 -0
- WMCore/DataStructs/WMObject.py +47 -0
- WMCore/DataStructs/WorkUnit.py +112 -0
- WMCore/DataStructs/Workflow.py +60 -0
- WMCore/DataStructs/__init__.py +8 -0
- WMCore/Database/CMSCouch.py +1349 -0
- WMCore/Database/ConfigDBMap.py +29 -0
- WMCore/Database/CouchUtils.py +118 -0
- WMCore/Database/DBCore.py +198 -0
- WMCore/Database/DBCreator.py +113 -0
- WMCore/Database/DBExceptionHandler.py +57 -0
- WMCore/Database/DBFactory.py +110 -0
- WMCore/Database/DBFormatter.py +177 -0
- WMCore/Database/Dialects.py +13 -0
- WMCore/Database/ExecuteDAO.py +327 -0
- WMCore/Database/MongoDB.py +241 -0
- WMCore/Database/MySQL/Destroy.py +42 -0
- WMCore/Database/MySQL/ListUserContent.py +20 -0
- WMCore/Database/MySQL/__init__.py +9 -0
- WMCore/Database/MySQLCore.py +132 -0
- WMCore/Database/Oracle/Destroy.py +56 -0
- WMCore/Database/Oracle/ListUserContent.py +19 -0
- WMCore/Database/Oracle/__init__.py +9 -0
- WMCore/Database/ResultSet.py +44 -0
- WMCore/Database/Transaction.py +91 -0
- WMCore/Database/__init__.py +9 -0
- WMCore/Database/ipy_profile_couch.py +438 -0
- WMCore/GlobalWorkQueue/CherryPyThreads/CleanUpTask.py +29 -0
- WMCore/GlobalWorkQueue/CherryPyThreads/HeartbeatMonitor.py +105 -0
- WMCore/GlobalWorkQueue/CherryPyThreads/LocationUpdateTask.py +28 -0
- WMCore/GlobalWorkQueue/CherryPyThreads/ReqMgrInteractionTask.py +35 -0
- WMCore/GlobalWorkQueue/CherryPyThreads/__init__.py +0 -0
- WMCore/GlobalWorkQueue/__init__.py +0 -0
- WMCore/GroupUser/CouchObject.py +127 -0
- WMCore/GroupUser/Decorators.py +51 -0
- WMCore/GroupUser/Group.py +33 -0
- WMCore/GroupUser/Interface.py +73 -0
- WMCore/GroupUser/User.py +96 -0
- WMCore/GroupUser/__init__.py +11 -0
- WMCore/Lexicon.py +836 -0
- WMCore/REST/Auth.py +202 -0
- WMCore/REST/CherryPyPeriodicTask.py +166 -0
- WMCore/REST/Error.py +333 -0
- WMCore/REST/Format.py +642 -0
- WMCore/REST/HeartbeatMonitorBase.py +90 -0
- WMCore/REST/Main.py +623 -0
- WMCore/REST/Server.py +2435 -0
- WMCore/REST/Services.py +24 -0
- WMCore/REST/Test.py +120 -0
- WMCore/REST/Tools.py +38 -0
- WMCore/REST/Validation.py +250 -0
- WMCore/REST/__init__.py +1 -0
- WMCore/ReqMgr/DataStructs/RequestStatus.py +209 -0
- WMCore/ReqMgr/DataStructs/RequestType.py +13 -0
- WMCore/ReqMgr/DataStructs/__init__.py +0 -0
- WMCore/ReqMgr/__init__.py +1 -0
- WMCore/Services/AlertManager/AlertManagerAPI.py +111 -0
- WMCore/Services/AlertManager/__init__.py +0 -0
- WMCore/Services/CRIC/CRIC.py +238 -0
- WMCore/Services/CRIC/__init__.py +0 -0
- WMCore/Services/DBS/DBS3Reader.py +1044 -0
- WMCore/Services/DBS/DBSConcurrency.py +44 -0
- WMCore/Services/DBS/DBSErrors.py +113 -0
- WMCore/Services/DBS/DBSReader.py +23 -0
- WMCore/Services/DBS/DBSUtils.py +139 -0
- WMCore/Services/DBS/DBSWriterObjects.py +381 -0
- WMCore/Services/DBS/ProdException.py +133 -0
- WMCore/Services/DBS/__init__.py +8 -0
- WMCore/Services/FWJRDB/FWJRDBAPI.py +118 -0
- WMCore/Services/FWJRDB/__init__.py +0 -0
- WMCore/Services/HTTPS/HTTPSAuthHandler.py +66 -0
- WMCore/Services/HTTPS/__init__.py +0 -0
- WMCore/Services/LogDB/LogDB.py +201 -0
- WMCore/Services/LogDB/LogDBBackend.py +191 -0
- WMCore/Services/LogDB/LogDBExceptions.py +11 -0
- WMCore/Services/LogDB/LogDBReport.py +85 -0
- WMCore/Services/LogDB/__init__.py +0 -0
- WMCore/Services/MSPileup/__init__.py +0 -0
- WMCore/Services/MSUtils/MSUtils.py +54 -0
- WMCore/Services/MSUtils/__init__.py +0 -0
- WMCore/Services/McM/McM.py +173 -0
- WMCore/Services/McM/__init__.py +8 -0
- WMCore/Services/MonIT/Grafana.py +133 -0
- WMCore/Services/MonIT/__init__.py +0 -0
- WMCore/Services/PyCondor/PyCondorAPI.py +154 -0
- WMCore/Services/PyCondor/PyCondorUtils.py +105 -0
- WMCore/Services/PyCondor/__init__.py +0 -0
- WMCore/Services/ReqMgr/ReqMgr.py +261 -0
- WMCore/Services/ReqMgr/__init__.py +0 -0
- WMCore/Services/ReqMgrAux/ReqMgrAux.py +419 -0
- WMCore/Services/ReqMgrAux/__init__.py +0 -0
- WMCore/Services/RequestDB/RequestDBReader.py +267 -0
- WMCore/Services/RequestDB/RequestDBWriter.py +39 -0
- WMCore/Services/RequestDB/__init__.py +0 -0
- WMCore/Services/Requests.py +624 -0
- WMCore/Services/Rucio/Rucio.py +1287 -0
- WMCore/Services/Rucio/RucioUtils.py +74 -0
- WMCore/Services/Rucio/__init__.py +0 -0
- WMCore/Services/RucioConMon/RucioConMon.py +128 -0
- WMCore/Services/RucioConMon/__init__.py +0 -0
- WMCore/Services/Service.py +400 -0
- WMCore/Services/StompAMQ/__init__.py +0 -0
- WMCore/Services/TagCollector/TagCollector.py +155 -0
- WMCore/Services/TagCollector/XMLUtils.py +98 -0
- WMCore/Services/TagCollector/__init__.py +0 -0
- WMCore/Services/UUIDLib.py +13 -0
- WMCore/Services/UserFileCache/UserFileCache.py +160 -0
- WMCore/Services/UserFileCache/__init__.py +8 -0
- WMCore/Services/WMAgent/WMAgent.py +63 -0
- WMCore/Services/WMAgent/__init__.py +0 -0
- WMCore/Services/WMArchive/CMSSWMetrics.py +526 -0
- WMCore/Services/WMArchive/DataMap.py +463 -0
- WMCore/Services/WMArchive/WMArchive.py +33 -0
- WMCore/Services/WMArchive/__init__.py +0 -0
- WMCore/Services/WMBS/WMBS.py +97 -0
- WMCore/Services/WMBS/__init__.py +0 -0
- WMCore/Services/WMStats/DataStruct/RequestInfoCollection.py +300 -0
- WMCore/Services/WMStats/DataStruct/__init__.py +0 -0
- WMCore/Services/WMStats/WMStatsPycurl.py +145 -0
- WMCore/Services/WMStats/WMStatsReader.py +445 -0
- WMCore/Services/WMStats/WMStatsWriter.py +273 -0
- WMCore/Services/WMStats/__init__.py +0 -0
- WMCore/Services/WMStatsServer/WMStatsServer.py +134 -0
- WMCore/Services/WMStatsServer/__init__.py +0 -0
- WMCore/Services/WorkQueue/WorkQueue.py +492 -0
- WMCore/Services/WorkQueue/__init__.py +0 -0
- WMCore/Services/__init__.py +8 -0
- WMCore/Services/pycurl_manager.py +574 -0
- WMCore/WMBase.py +50 -0
- WMCore/WMConnectionBase.py +164 -0
- WMCore/WMException.py +183 -0
- WMCore/WMExceptions.py +269 -0
- WMCore/WMFactory.py +76 -0
- WMCore/WMInit.py +228 -0
- WMCore/WMLogging.py +108 -0
- WMCore/WMSpec/ConfigSectionTree.py +442 -0
- WMCore/WMSpec/Persistency.py +135 -0
- WMCore/WMSpec/Steps/BuildMaster.py +87 -0
- WMCore/WMSpec/Steps/BuildTools.py +201 -0
- WMCore/WMSpec/Steps/Builder.py +97 -0
- WMCore/WMSpec/Steps/Diagnostic.py +89 -0
- WMCore/WMSpec/Steps/Emulator.py +62 -0
- WMCore/WMSpec/Steps/ExecuteMaster.py +208 -0
- WMCore/WMSpec/Steps/Executor.py +210 -0
- WMCore/WMSpec/Steps/StepFactory.py +213 -0
- WMCore/WMSpec/Steps/TaskEmulator.py +75 -0
- WMCore/WMSpec/Steps/Template.py +204 -0
- WMCore/WMSpec/Steps/Templates/AlcaHarvest.py +76 -0
- WMCore/WMSpec/Steps/Templates/CMSSW.py +613 -0
- WMCore/WMSpec/Steps/Templates/DQMUpload.py +59 -0
- WMCore/WMSpec/Steps/Templates/DeleteFiles.py +70 -0
- WMCore/WMSpec/Steps/Templates/LogArchive.py +84 -0
- WMCore/WMSpec/Steps/Templates/LogCollect.py +105 -0
- WMCore/WMSpec/Steps/Templates/StageOut.py +105 -0
- WMCore/WMSpec/Steps/Templates/__init__.py +10 -0
- WMCore/WMSpec/Steps/WMExecutionFailure.py +21 -0
- WMCore/WMSpec/Steps/__init__.py +8 -0
- WMCore/WMSpec/Utilities.py +63 -0
- WMCore/WMSpec/WMSpecErrors.py +12 -0
- WMCore/WMSpec/WMStep.py +347 -0
- WMCore/WMSpec/WMTask.py +1980 -0
- WMCore/WMSpec/WMWorkload.py +2288 -0
- WMCore/WMSpec/WMWorkloadTools.py +370 -0
- WMCore/WMSpec/__init__.py +9 -0
- WMCore/WorkQueue/DataLocationMapper.py +273 -0
- WMCore/WorkQueue/DataStructs/ACDCBlock.py +47 -0
- WMCore/WorkQueue/DataStructs/Block.py +48 -0
- WMCore/WorkQueue/DataStructs/CouchWorkQueueElement.py +148 -0
- WMCore/WorkQueue/DataStructs/WorkQueueElement.py +274 -0
- WMCore/WorkQueue/DataStructs/WorkQueueElementResult.py +152 -0
- WMCore/WorkQueue/DataStructs/WorkQueueElementsSummary.py +185 -0
- WMCore/WorkQueue/DataStructs/__init__.py +0 -0
- WMCore/WorkQueue/Policy/End/EndPolicyInterface.py +44 -0
- WMCore/WorkQueue/Policy/End/SingleShot.py +22 -0
- WMCore/WorkQueue/Policy/End/__init__.py +32 -0
- WMCore/WorkQueue/Policy/PolicyInterface.py +17 -0
- WMCore/WorkQueue/Policy/Start/Block.py +258 -0
- WMCore/WorkQueue/Policy/Start/Dataset.py +180 -0
- WMCore/WorkQueue/Policy/Start/MonteCarlo.py +131 -0
- WMCore/WorkQueue/Policy/Start/ResubmitBlock.py +171 -0
- WMCore/WorkQueue/Policy/Start/StartPolicyInterface.py +316 -0
- WMCore/WorkQueue/Policy/Start/__init__.py +34 -0
- WMCore/WorkQueue/Policy/__init__.py +57 -0
- WMCore/WorkQueue/WMBSHelper.py +772 -0
- WMCore/WorkQueue/WorkQueue.py +1237 -0
- WMCore/WorkQueue/WorkQueueBackend.py +750 -0
- WMCore/WorkQueue/WorkQueueBase.py +39 -0
- WMCore/WorkQueue/WorkQueueExceptions.py +44 -0
- WMCore/WorkQueue/WorkQueueReqMgrInterface.py +278 -0
- WMCore/WorkQueue/WorkQueueUtils.py +130 -0
- WMCore/WorkQueue/__init__.py +13 -0
- WMCore/Wrappers/JsonWrapper/JSONThunker.py +342 -0
- WMCore/Wrappers/JsonWrapper/__init__.py +7 -0
- WMCore/Wrappers/__init__.py +6 -0
- WMCore/__init__.py +10 -0
- wmglobalqueue-2.3.10.data/data/bin/wmc-dist-patch +15 -0
- wmglobalqueue-2.3.10.data/data/bin/wmc-dist-unpatch +8 -0
- wmglobalqueue-2.3.10.data/data/bin/wmc-httpd +3 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/.couchapprc +1 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/README.md +40 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/_attachments/index.html +264 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/_attachments/js/ElementInfoByWorkflow.js +96 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/_attachments/js/StuckElementInfo.js +57 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/_attachments/js/WorkloadInfoTable.js +80 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/_attachments/js/dataTable.js +70 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/_attachments/js/namespace.js +23 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/_attachments/style/main.css +75 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/couchapp.json +4 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/filters/childQueueFilter.js +13 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/filters/filterDeletedDocs.js +3 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/filters/queueFilter.js +11 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/language +1 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/lib/mustache.js +333 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/lib/validate.js +27 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/lib/workqueue_utils.js +61 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/lists/elementsDetail.js +28 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/lists/filter.js +86 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/lists/stuckElements.js +38 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/lists/workRestrictions.js +153 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/lists/workflowSummary.js +28 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/rewrites.json +73 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/shows/redirect.js +23 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/shows/status.js +40 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/templates/ElementSummaryByWorkflow.html +27 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/templates/StuckElementSummary.html +26 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/templates/TaskStatus.html +23 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/templates/WorkflowSummary.html +27 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/templates/partials/workqueue-common-lib.html +2 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/templates/partials/yui-lib-remote.html +16 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/templates/partials/yui-lib.html +18 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/updates/in-place.js +50 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/validate_doc_update.js +8 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/vendor/couchapp/_attachments/jquery.couch.app.js +235 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/vendor/couchapp/_attachments/jquery.pathbinder.js +173 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/activeData/map.js +8 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/activeData/reduce.js +2 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/activeParentData/map.js +8 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/activeParentData/reduce.js +2 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/activePileupData/map.js +8 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/activePileupData/reduce.js +2 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/analyticsData/map.js +11 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/analyticsData/reduce.js +1 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/availableByPriority/map.js +6 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/conflicts/map.js +5 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/elements/map.js +5 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/elementsByData/map.js +8 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/elementsByParent/map.js +8 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/elementsByParentData/map.js +8 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/elementsByPileupData/map.js +8 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/elementsByStatus/map.js +8 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/elementsBySubscription/map.js +6 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/elementsByWorkflow/map.js +8 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/elementsByWorkflow/reduce.js +3 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/elementsDetailByWorkflowAndStatus/map.js +26 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/jobInjectStatusByRequest/map.js +10 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/jobInjectStatusByRequest/reduce.js +1 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/jobStatusByRequest/map.js +6 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/jobStatusByRequest/reduce.js +1 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/jobsByChildQueueAndPriority/map.js +6 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/jobsByChildQueueAndPriority/reduce.js +1 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/jobsByChildQueueAndStatus/map.js +6 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/jobsByChildQueueAndStatus/reduce.js +1 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/jobsByRequest/map.js +6 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/jobsByRequest/reduce.js +1 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/jobsByStatus/map.js +6 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/jobsByStatus/reduce.js +1 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/jobsByStatusAndPriority/map.js +6 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/jobsByStatusAndPriority/reduce.js +1 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/openRequests/map.js +6 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/recent-items/map.js +5 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/siteWhitelistByRequest/map.js +6 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/siteWhitelistByRequest/reduce.js +1 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/specsByWorkflow/map.js +5 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/stuckElements/map.js +38 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/wmbsInjectStatusByRequest/map.js +12 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/wmbsInjectStatusByRequest/reduce.js +3 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/wmbsUrl/map.js +6 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/wmbsUrl/reduce.js +2 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/wmbsUrlByRequest/map.js +6 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/wmbsUrlByRequest/reduce.js +2 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/workflowSummary/map.js +9 -0
- wmglobalqueue-2.3.10.data/data/data/couchapps/WorkQueue/views/workflowSummary/reduce.js +10 -0
- wmglobalqueue-2.3.10.dist-info/LICENSE +202 -0
- wmglobalqueue-2.3.10.dist-info/METADATA +24 -0
- wmglobalqueue-2.3.10.dist-info/NOTICE +16 -0
- wmglobalqueue-2.3.10.dist-info/RECORD +345 -0
- wmglobalqueue-2.3.10.dist-info/WHEEL +5 -0
- wmglobalqueue-2.3.10.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,1287 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# coding=utf-8
|
|
3
|
+
"""
|
|
4
|
+
Rucio Service class developed on top of the native Rucio Client
|
|
5
|
+
APIs providing custom output and handling as necessary for the
|
|
6
|
+
CMS Workload Management system (and migration from PhEDEx).
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import division, print_function, absolute_import
|
|
9
|
+
|
|
10
|
+
from builtins import str, object
|
|
11
|
+
from future.utils import viewitems, viewvalues
|
|
12
|
+
|
|
13
|
+
from copy import deepcopy
|
|
14
|
+
import json
|
|
15
|
+
import logging
|
|
16
|
+
|
|
17
|
+
from rucio.client import Client
|
|
18
|
+
from rucio.common.exception import (AccountNotFound, DataIdentifierNotFound, AccessDenied, DuplicateRule,
|
|
19
|
+
DataIdentifierAlreadyExists, DuplicateContent, InvalidRSEExpression,
|
|
20
|
+
UnsupportedOperation, FileAlreadyExists, RuleNotFound, RSENotFound, RuleReplaceFailed)
|
|
21
|
+
from Utils.MemoryCache import MemoryCache
|
|
22
|
+
from Utils.IteratorTools import grouper
|
|
23
|
+
from WMCore.Services.Rucio.RucioUtils import (validateMetaData, weightedChoice,
|
|
24
|
+
isTapeRSE, dropTapeRSEs)
|
|
25
|
+
from WMCore.WMException import WMException
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class WMRucioException(WMException):
|
|
29
|
+
"""
|
|
30
|
+
_WMRucioException_
|
|
31
|
+
Generic WMCore exception for Rucio
|
|
32
|
+
"""
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class WMRucioDIDNotFoundException(WMException):
|
|
37
|
+
"""
|
|
38
|
+
_WMRucioDIDNotFoundException_
|
|
39
|
+
Generic WMCore exception for Rucio
|
|
40
|
+
"""
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Rucio(object):
|
|
45
|
+
"""
|
|
46
|
+
Service class providing additional Rucio functionality on top of the
|
|
47
|
+
Rucio client APIs.
|
|
48
|
+
|
|
49
|
+
Just a reminder, we have different naming convention between Rucio
|
|
50
|
+
and CMS, where:
|
|
51
|
+
* CMS dataset corresponds to a Rucio container
|
|
52
|
+
* CMS block corresponds to a Rucio dataset
|
|
53
|
+
* CMS file corresponds to a Rucio file
|
|
54
|
+
We will try to use Container -> CMS dataset, block -> CMS block.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(self, acct, hostUrl=None, authUrl=None, configDict=None, client=None):
|
|
58
|
+
"""
|
|
59
|
+
Constructs a Rucio object with the Client object embedded.
|
|
60
|
+
In order to instantiate a Rucio Client object, it assumes the host has
|
|
61
|
+
a proper rucio configuration file, where the default host and authentication
|
|
62
|
+
host URL come from, as well as the X509 certificate information.
|
|
63
|
+
:param acct: rucio account to be used
|
|
64
|
+
:param hostUrl: defaults to the rucio config one
|
|
65
|
+
:param authUrl: defaults to the rucio config one
|
|
66
|
+
:param configDict: dictionary with extra parameters
|
|
67
|
+
:param client: optional Rucio client to use (useful for mock-up)
|
|
68
|
+
"""
|
|
69
|
+
configDict = configDict or {}
|
|
70
|
+
# default RSE data caching to 12h
|
|
71
|
+
rseCacheExpiration = configDict.pop('cacheExpiration', 12 * 60 * 60)
|
|
72
|
+
self.logger = configDict.pop("logger", logging.getLogger())
|
|
73
|
+
|
|
74
|
+
self.rucioParams = deepcopy(configDict)
|
|
75
|
+
self.rucioParams.setdefault('account', acct)
|
|
76
|
+
self.rucioParams.setdefault('rucio_host', hostUrl)
|
|
77
|
+
self.rucioParams.setdefault('auth_host', authUrl)
|
|
78
|
+
self.rucioParams.setdefault('ca_cert', None)
|
|
79
|
+
self.rucioParams.setdefault('auth_type', None)
|
|
80
|
+
self.rucioParams.setdefault('creds', None)
|
|
81
|
+
self.rucioParams.setdefault('timeout', 600)
|
|
82
|
+
self.rucioParams.setdefault('user_agent', 'wmcore-client')
|
|
83
|
+
|
|
84
|
+
self.logger.info("WMCore Rucio initialization parameters: %s", self.rucioParams)
|
|
85
|
+
if client:
|
|
86
|
+
self.cli = client
|
|
87
|
+
else:
|
|
88
|
+
self.cli = Client(rucio_host=hostUrl, auth_host=authUrl, account=acct,
|
|
89
|
+
ca_cert=self.rucioParams['ca_cert'], auth_type=self.rucioParams['auth_type'],
|
|
90
|
+
creds=self.rucioParams['creds'], timeout=self.rucioParams['timeout'],
|
|
91
|
+
user_agent=self.rucioParams['user_agent'])
|
|
92
|
+
clientParams = {}
|
|
93
|
+
for k in ("host", "auth_host", "auth_type", "account", "user_agent",
|
|
94
|
+
"ca_cert", "creds", "timeout", "request_retries"):
|
|
95
|
+
clientParams[k] = getattr(self.cli, k)
|
|
96
|
+
self.logger.info("Rucio client initialization parameters: %s", clientParams)
|
|
97
|
+
|
|
98
|
+
# keep a map of rse expression to RSE names mapped for some time
|
|
99
|
+
self.cachedRSEs = MemoryCache(rseCacheExpiration, {})
|
|
100
|
+
|
|
101
|
+
def pingServer(self):
|
|
102
|
+
"""
|
|
103
|
+
_pingServer_
|
|
104
|
+
|
|
105
|
+
Ping the rucio server to see whether it's alive
|
|
106
|
+
:return: a dict with the server version
|
|
107
|
+
"""
|
|
108
|
+
return self.cli.ping()
|
|
109
|
+
|
|
110
|
+
def whoAmI(self):
|
|
111
|
+
"""
|
|
112
|
+
_whoAmI_
|
|
113
|
+
|
|
114
|
+
Get information about account whose token is used
|
|
115
|
+
:return: a dict with the account information. None in case of failure
|
|
116
|
+
"""
|
|
117
|
+
return self.cli.whoami()
|
|
118
|
+
|
|
119
|
+
def getAccount(self, acct):
|
|
120
|
+
"""
|
|
121
|
+
_getAccount_
|
|
122
|
+
|
|
123
|
+
Gets information about a specific account
|
|
124
|
+
:return: a dict with the account information. None in case of failure
|
|
125
|
+
"""
|
|
126
|
+
res = None
|
|
127
|
+
try:
|
|
128
|
+
res = self.cli.get_account(acct)
|
|
129
|
+
except (AccountNotFound, AccessDenied) as ex:
|
|
130
|
+
self.logger.error("Failed to get account information from Rucio. Error: %s", str(ex))
|
|
131
|
+
return res
|
|
132
|
+
|
|
133
|
+
def getAccountLimits(self, acct):
|
|
134
|
+
"""
|
|
135
|
+
Provided an account name, fetch the storage quota for all RSEs
|
|
136
|
+
:param acct: a string with the rucio account name
|
|
137
|
+
:return: a dictionary of RSE name and quota in bytes.
|
|
138
|
+
"""
|
|
139
|
+
res = {}
|
|
140
|
+
try:
|
|
141
|
+
res = self.cli.get_local_account_limits(acct)
|
|
142
|
+
except AccountNotFound as ex:
|
|
143
|
+
self.logger.error("Account: %s not found in the Rucio Server. Error: %s", acct, str(ex))
|
|
144
|
+
return res
|
|
145
|
+
|
|
146
|
+
def getAccountUsage(self, acct, rse=None):
|
|
147
|
+
"""
|
|
148
|
+
_getAccountUsage_
|
|
149
|
+
|
|
150
|
+
Provided an account name, gets the storage usage for it against
|
|
151
|
+
a given RSE (or all RSEs)
|
|
152
|
+
:param acct: a string with the rucio account name
|
|
153
|
+
:param rse: an optional string with the RSE name
|
|
154
|
+
:return: a list of dictionaries with the account usage information.
|
|
155
|
+
None in case of failure
|
|
156
|
+
"""
|
|
157
|
+
res = []
|
|
158
|
+
try:
|
|
159
|
+
res = list(self.cli.get_local_account_usage(acct, rse=rse))
|
|
160
|
+
except (AccountNotFound, AccessDenied) as ex:
|
|
161
|
+
self.logger.error("Failed to get account usage information from Rucio. Error: %s", str(ex))
|
|
162
|
+
return res
|
|
163
|
+
|
|
164
|
+
def getBlocksInContainer(self, container, scope='cms'):
|
|
165
|
+
"""
|
|
166
|
+
_getBlocksInContainer_
|
|
167
|
+
|
|
168
|
+
Provided a Rucio container - CMS dataset - retrieve all blocks in it.
|
|
169
|
+
:param container: a CMS dataset string
|
|
170
|
+
:param scope: string containing the Rucio scope (defaults to 'cms')
|
|
171
|
+
:return: a list of block names
|
|
172
|
+
"""
|
|
173
|
+
blockNames = []
|
|
174
|
+
if not self.isContainer(container, scope=scope):
|
|
175
|
+
# input container wasn't really a container
|
|
176
|
+
self.logger.warning("Provided DID name is not a CONTAINER type: %s", container)
|
|
177
|
+
return blockNames
|
|
178
|
+
|
|
179
|
+
response = self.cli.list_content(scope=scope, name=container)
|
|
180
|
+
for item in response:
|
|
181
|
+
if item['type'].upper() == 'DATASET':
|
|
182
|
+
blockNames.append(item['name'])
|
|
183
|
+
|
|
184
|
+
return blockNames
|
|
185
|
+
|
|
186
|
+
def getReplicaInfoForBlocks(self, **kwargs):
|
|
187
|
+
"""
|
|
188
|
+
_getReplicaInfoForBlocks_
|
|
189
|
+
|
|
190
|
+
Get block replica information.
|
|
191
|
+
It used to mimic the same PhEDEx wrapper API, listing all the
|
|
192
|
+
current locations where the provided input data is.
|
|
193
|
+
|
|
194
|
+
:param kwargs: Supported keyword arguments:
|
|
195
|
+
* block: List of either dataset or block names. Not both!,
|
|
196
|
+
* deep: Whether or not to lookup for replica info at file level
|
|
197
|
+
:return: a list of dictionaries with replica information
|
|
198
|
+
"""
|
|
199
|
+
kwargs.setdefault("scope", "cms")
|
|
200
|
+
kwargs.setdefault("deep", False)
|
|
201
|
+
|
|
202
|
+
blockNames = []
|
|
203
|
+
result = []
|
|
204
|
+
|
|
205
|
+
if isinstance(kwargs.get('block', None), (list, set)):
|
|
206
|
+
blockNames = kwargs['block']
|
|
207
|
+
elif 'block' in kwargs:
|
|
208
|
+
blockNames = [kwargs['block']]
|
|
209
|
+
|
|
210
|
+
if isinstance(kwargs.get('dataset', None), (list, set)):
|
|
211
|
+
for datasetName in kwargs['dataset']:
|
|
212
|
+
blockNames.extend(self.getBlocksInContainer(datasetName, scope=kwargs['scope']))
|
|
213
|
+
elif 'dataset' in kwargs:
|
|
214
|
+
blockNames.extend(self.getBlocksInContainer(kwargs['dataset'], scope=kwargs['scope']))
|
|
215
|
+
|
|
216
|
+
inputDids = []
|
|
217
|
+
for block in blockNames:
|
|
218
|
+
inputDids.append({"scope": kwargs["scope"], "type": "DATASET", "name": block})
|
|
219
|
+
|
|
220
|
+
resultDict = {}
|
|
221
|
+
if kwargs['deep']:
|
|
222
|
+
for did in inputDids:
|
|
223
|
+
for item in self.cli.list_dataset_replicas(kwargs["scope"], did["name"], deep=kwargs['deep']):
|
|
224
|
+
resultDict.setdefault(item['name'], [])
|
|
225
|
+
if item['state'].upper() == 'AVAILABLE':
|
|
226
|
+
resultDict[item['name']].append(item['rse'])
|
|
227
|
+
else:
|
|
228
|
+
for item in self.cli.list_dataset_replicas_bulk(inputDids):
|
|
229
|
+
resultDict.setdefault(item['name'], [])
|
|
230
|
+
if item['state'].upper() == 'AVAILABLE':
|
|
231
|
+
resultDict[item['name']].append(item['rse'])
|
|
232
|
+
|
|
233
|
+
# Finally, convert it to a list of dictionaries, like:
|
|
234
|
+
# [{"name": "block_A", "replica": ["nodeA", "nodeB"]},
|
|
235
|
+
# {"name": "block_B", etc etc}]
|
|
236
|
+
for blockName, rses in viewitems(resultDict):
|
|
237
|
+
result.append({"name": blockName, "replica": list(set(rses))})
|
|
238
|
+
|
|
239
|
+
return result
|
|
240
|
+
|
|
241
|
+
def getPFN(self, site, lfns, protocol=None, protocolDomain='ALL', operation=None):
|
|
242
|
+
"""
|
|
243
|
+
returns a list of PFN(s) for a list of LFN(s) and one site
|
|
244
|
+
Note: same function implemented for PhEDEx accepted a list of sites, but semantic was obscure and actual need
|
|
245
|
+
unknown. So take this chance to make things simple.
|
|
246
|
+
See here for documentation of the upstream Rucio API: https://rucio.readthedocs.io/en/latest/api/rse.html
|
|
247
|
+
:param site: a Rucio RSE, i.e. a site name in standard CMS format like 'T1_UK_RAL_Disk' or 'T2_CH_CERN'
|
|
248
|
+
:param lfns: one LFN or a list of LFN's, does not need to correspond to actual files and could be a top level directory
|
|
249
|
+
like ['/store/user/rucio/jdoe','/store/data',...] or the simple '/store/data'
|
|
250
|
+
:param protocol: If the RSE supports multiple access protocols, a preferred protocol can be selected via this,
|
|
251
|
+
otherwise the default one for the site will be selected. Example: 'gsiftp' or 'davs'
|
|
252
|
+
this is what is called "scheme" in the RUCIO API (we think protocol is more clear)
|
|
253
|
+
:param protocolDomain: The scope of the protocol. Supported are "LAN", "WAN", and "ALL" (as default)
|
|
254
|
+
this is what is called protocol_domain in RUCIO API, changed for CMS camelCase convention
|
|
255
|
+
:param operation: The name of the requested operation (read, write, or delete). If None, all operations are queried
|
|
256
|
+
:return: a dictionary having the LFN's as keys and the corresponding PFN's as values.
|
|
257
|
+
|
|
258
|
+
Will raise a Rucio exception if input is wrong.
|
|
259
|
+
"""
|
|
260
|
+
|
|
261
|
+
# allow for duck typing, if a single lfn was passed, make it a list
|
|
262
|
+
# avoid handling a string like a list of chars !
|
|
263
|
+
if not isinstance(lfns, (set, list)):
|
|
264
|
+
lfns = [lfns]
|
|
265
|
+
|
|
266
|
+
# add a scope to turn LFNs into Rucio DID syntax
|
|
267
|
+
dids = ['cms:' + lfn for lfn in lfns]
|
|
268
|
+
|
|
269
|
+
# Rucio's lfns2pfns returns a dictionary with did as key and pfn as value:
|
|
270
|
+
# {u'cms:/store/user/rucio': u'gsiftp://red-gridftp.unl.edu:2811/mnt/hadoop/user/uscms01/pnfs/unl.edu/data4/cms/store/user/rucio'}
|
|
271
|
+
didDict = self.cli.lfns2pfns(site, dids, scheme=protocol, protocol_domain=protocolDomain, operation=operation)
|
|
272
|
+
|
|
273
|
+
# convert to a more useful format for us with LFN's as keys
|
|
274
|
+
pfnDict = {}
|
|
275
|
+
for oldKey in didDict:
|
|
276
|
+
newKey = oldKey.lstrip('cms:')
|
|
277
|
+
pfnDict[newKey] = didDict[oldKey]
|
|
278
|
+
|
|
279
|
+
return pfnDict
|
|
280
|
+
|
|
281
|
+
def createContainer(self, name, scope='cms', **kwargs):
|
|
282
|
+
"""
|
|
283
|
+
_createContainer_
|
|
284
|
+
|
|
285
|
+
Create a CMS dataset (Rucio container) in a given scope.
|
|
286
|
+
:param name: string with the container name
|
|
287
|
+
:param scope: optional string with the scope name
|
|
288
|
+
:param kwargs: supported keyword arguments (from the Rucio CLI API documentation):
|
|
289
|
+
* statuses: Dictionary with statuses, e.g.g {"monotonic":True}.
|
|
290
|
+
* meta: Meta-data associated with the data identifier is represented using key/value
|
|
291
|
+
pairs in a dictionary.
|
|
292
|
+
* rules: Replication rules associated with the data identifier. A list of dictionaries,
|
|
293
|
+
e.g., [{"copies": 2, "rse_expression": "TIERS1"}, ].
|
|
294
|
+
* lifetime: DID's lifetime (in seconds).
|
|
295
|
+
:return: a boolean to represent whether it succeeded or not
|
|
296
|
+
"""
|
|
297
|
+
response = False
|
|
298
|
+
if not validateMetaData(name, kwargs.get("meta", {}), logger=self.logger):
|
|
299
|
+
return response
|
|
300
|
+
try:
|
|
301
|
+
# add_container(scope, name, statuses=None, meta=None, rules=None, lifetime=None)
|
|
302
|
+
response = self.cli.add_container(scope, name, **kwargs)
|
|
303
|
+
except DataIdentifierAlreadyExists:
|
|
304
|
+
self.logger.debug("Container name already exists in Rucio: %s", name)
|
|
305
|
+
response = True
|
|
306
|
+
except Exception as ex:
|
|
307
|
+
self.logger.error("Exception creating container: %s. Error: %s", name, str(ex))
|
|
308
|
+
return response
|
|
309
|
+
|
|
310
|
+
def createBlock(self, name, scope='cms', attach=True, **kwargs):
|
|
311
|
+
"""
|
|
312
|
+
_createBlock_
|
|
313
|
+
|
|
314
|
+
Create a CMS block (Rucio dataset) in a given scope. It also associates
|
|
315
|
+
the block to its container
|
|
316
|
+
:param name: string with the block name
|
|
317
|
+
:param scope: optional string with the scope name
|
|
318
|
+
:param attach: boolean whether to attack this block to its container or not
|
|
319
|
+
:param kwargs: supported keyword arguments (from the Rucio CLI API documentation):
|
|
320
|
+
* statuses: Dictionary with statuses, e.g. {"monotonic":True}.
|
|
321
|
+
* lifetime: DID's lifetime (in seconds).
|
|
322
|
+
* files: The content.
|
|
323
|
+
* rse: The RSE name when registering replicas.
|
|
324
|
+
* meta: Meta-data associated with the data identifier. Represented
|
|
325
|
+
using key/value pairs in a dictionary.
|
|
326
|
+
* rules: replication rules associated with the data identifier. A list of
|
|
327
|
+
dictionaries, e.g., [{"copies": 2, "rse_expression": "TIERS1"}, ].
|
|
328
|
+
:return: a boolean with the outcome of the operation
|
|
329
|
+
|
|
330
|
+
NOTE: we will very likely define a rule saying: keep this block at the location it's
|
|
331
|
+
being produced
|
|
332
|
+
"""
|
|
333
|
+
response = False
|
|
334
|
+
if not validateMetaData(name, kwargs.get("meta", {}), logger=self.logger):
|
|
335
|
+
return response
|
|
336
|
+
try:
|
|
337
|
+
# add_dataset(scope, name, statuses=None, meta=None, rules=None, lifetime=None, files=None, rse=None)
|
|
338
|
+
response = self.cli.add_dataset(scope, name, **kwargs)
|
|
339
|
+
except DataIdentifierAlreadyExists:
|
|
340
|
+
self.logger.debug("Block name already exists in Rucio: %s", name)
|
|
341
|
+
response = True
|
|
342
|
+
except Exception as ex:
|
|
343
|
+
self.logger.error("Exception creating block: %s. Error: %s", name, str(ex))
|
|
344
|
+
|
|
345
|
+
# then attach this block recently created to its container
|
|
346
|
+
if response and attach:
|
|
347
|
+
container = name.split('#')[0]
|
|
348
|
+
response = self.attachDIDs(kwargs.get('rse'), container, name, scope)
|
|
349
|
+
return response
|
|
350
|
+
|
|
351
|
+
def attachDIDs(self, rse, superDID, dids, scope='cms', chunkSize=1000):
|
|
352
|
+
"""
|
|
353
|
+
_attachDIDs_
|
|
354
|
+
|
|
355
|
+
Create a list of files - in bulk - to a RSE. Then attach them to the block name.
|
|
356
|
+
:param rse: string with the RSE name
|
|
357
|
+
:param superDID: upper structure level (can be a container or block. If attaching blocks,
|
|
358
|
+
then it's a container name; if attaching files, then it's a block name)
|
|
359
|
+
:param dids: either a string or a list of data identifiers (can be block or files)
|
|
360
|
+
:param scope: string with the scope name
|
|
361
|
+
:param chunkSize: maximum number of dids to be attached in any single Rucio server call
|
|
362
|
+
:return: a boolean to represent whether it succeeded or not
|
|
363
|
+
"""
|
|
364
|
+
if not isinstance(dids, list):
|
|
365
|
+
dids = [dids]
|
|
366
|
+
# NOTE: the attaching dids do not create new container within a scope
|
|
367
|
+
# and it is safe to use cms scope for it
|
|
368
|
+
alldids = [{'scope': 'cms', 'name': did} for did in sorted(dids)]
|
|
369
|
+
|
|
370
|
+
# report if we use chunk size in rucio attach_dids API call
|
|
371
|
+
if len(alldids) > chunkSize:
|
|
372
|
+
self.logger.info("Attaching a total of %d DIDs in chunk size of: %d", len(alldids), chunkSize)
|
|
373
|
+
|
|
374
|
+
for dids in grouper(alldids, chunkSize):
|
|
375
|
+
response = False
|
|
376
|
+
try:
|
|
377
|
+
response = self.cli.attach_dids(scope, superDID, dids=dids, rse=rse)
|
|
378
|
+
except DuplicateContent:
|
|
379
|
+
self.logger.warning("Dids: %s already attached to: %s", dids, superDID)
|
|
380
|
+
response = True
|
|
381
|
+
except FileAlreadyExists:
|
|
382
|
+
self.logger.warning("Failed to attach files already existent on block: %s", superDID)
|
|
383
|
+
response = True
|
|
384
|
+
except DataIdentifierNotFound:
|
|
385
|
+
self.logger.error("Failed to attach dids: %s. Parent DID %s does not exist.", dids, superDID)
|
|
386
|
+
except Exception as ex:
|
|
387
|
+
self.logger.error("Exception attaching %s dids to: %s. Error: %s. First 10 dids: %s",
|
|
388
|
+
len(dids), superDID, str(ex), dids[:10])
|
|
389
|
+
if not response:
|
|
390
|
+
# if we had failure with specific chunk of dids we'll return immediately
|
|
391
|
+
return response
|
|
392
|
+
|
|
393
|
+
return response
|
|
394
|
+
|
|
395
|
+
def createReplicas(self, rse, files, block, scope='cms', ignoreAvailability=True):
|
|
396
|
+
"""
|
|
397
|
+
_createReplicas_
|
|
398
|
+
|
|
399
|
+
Create a list of files - in bulk - to a RSE. Then attach them to the block name.
|
|
400
|
+
:param rse: string with the RSE name
|
|
401
|
+
:param files: list of dictionaries with the file names and some of its meta data.
|
|
402
|
+
E.g.: {'name': lfn, 'bytes': size, 'scope': scope, 'adler32': checksum, 'state': 'A'}
|
|
403
|
+
State 'A' means that the replica is available
|
|
404
|
+
:param block: string with the block name
|
|
405
|
+
:param scope: string with the scope name
|
|
406
|
+
:param ignore_availability: boolean to ignore the RSE blacklisting
|
|
407
|
+
:return: a boolean to represent whether it succeeded or not
|
|
408
|
+
"""
|
|
409
|
+
if isinstance(files, dict):
|
|
410
|
+
files = [files]
|
|
411
|
+
for item in files:
|
|
412
|
+
item['scope'] = scope
|
|
413
|
+
|
|
414
|
+
response = False
|
|
415
|
+
try:
|
|
416
|
+
# add_replicas(rse, files, ignore_availability=True)
|
|
417
|
+
response = self.cli.add_replicas(rse, files, ignoreAvailability)
|
|
418
|
+
except DataIdentifierAlreadyExists as exc:
|
|
419
|
+
if len(files) == 1:
|
|
420
|
+
self.logger.debug("File replica already exists in Rucio: %s", files[0]['name'])
|
|
421
|
+
response = True
|
|
422
|
+
else:
|
|
423
|
+
# FIXME: I think we would have to iterate over every single file and add then one by one
|
|
424
|
+
errorMsg = "Failed to insert replicas for: {} and block: {}".format(files, block)
|
|
425
|
+
errorMsg += " Some/all DIDs already exist in Rucio. Error: {}".format(str(exc))
|
|
426
|
+
self.logger.error(errorMsg)
|
|
427
|
+
except Exception as exc:
|
|
428
|
+
self.logger.error("Failed to add replicas for: %s and block: %s. Error: %s", files, block, str(exc))
|
|
429
|
+
|
|
430
|
+
if response:
|
|
431
|
+
files = [item['name'] for item in files]
|
|
432
|
+
response = self.attachDIDs(rse, block, files, scope)
|
|
433
|
+
|
|
434
|
+
return response
|
|
435
|
+
|
|
436
|
+
def closeBlockContainer(self, name, scope='cms'):
|
|
437
|
+
"""
|
|
438
|
+
_closeBlockContainer_
|
|
439
|
+
|
|
440
|
+
Method to close a block or container, such that it doesn't get any more
|
|
441
|
+
blocks and/or files inserted.
|
|
442
|
+
:param name: data identifier (either a block or a container name)
|
|
443
|
+
:param scope: string with the scope name
|
|
444
|
+
:return: a boolean to represent whether it succeeded or not
|
|
445
|
+
"""
|
|
446
|
+
response = False
|
|
447
|
+
try:
|
|
448
|
+
response = self.cli.close(scope, name)
|
|
449
|
+
except UnsupportedOperation:
|
|
450
|
+
self.logger.warning("Container/block has been closed already: %s", name)
|
|
451
|
+
response = True
|
|
452
|
+
except DataIdentifierNotFound:
|
|
453
|
+
self.logger.error("Failed to close DID: %s; it does not exist.", name)
|
|
454
|
+
except Exception as ex:
|
|
455
|
+
self.logger.error("Exception closing container/block: %s. Error: %s", name, str(ex))
|
|
456
|
+
return response
|
|
457
|
+
|
|
458
|
+
def moveReplicationRule(self, ruleId, rseExpression, account):
|
|
459
|
+
"""
|
|
460
|
+
Perform move operation for provided rule id and rse expression
|
|
461
|
+
:param ruleId: rule id
|
|
462
|
+
:param rseExpression: rse expression
|
|
463
|
+
:param account: rucio quota account
|
|
464
|
+
:return: it returns either an empty list or a list with a string id for the rule created
|
|
465
|
+
Please note, we made return type from this wrapper compatible with createReplicateRule
|
|
466
|
+
"""
|
|
467
|
+
ruleIds = []
|
|
468
|
+
try:
|
|
469
|
+
rid = self.cli.move_replication_rule(ruleId, rseExpression, account)
|
|
470
|
+
ruleIds.append(rid)
|
|
471
|
+
except RuleNotFound as ex:
|
|
472
|
+
msg = "RuleNotFound move DID replication rule. Error: %s" % str(ex)
|
|
473
|
+
raise WMRucioException(msg) from ex
|
|
474
|
+
except RuleReplaceFailed as ex:
|
|
475
|
+
msg = "RuleReplaceFailed move DID replication rule. Error: %s" % str(ex)
|
|
476
|
+
raise WMRucioException(msg) from ex
|
|
477
|
+
except Exception as ex:
|
|
478
|
+
msg = "Unsupported exception from Rucio API. Error: %s" % str(ex)
|
|
479
|
+
raise WMRucioException(msg) from ex
|
|
480
|
+
return ruleIds
|
|
481
|
+
|
|
482
|
+
def createReplicationRule(self, names, rseExpression, scope='cms', copies=1, **kwargs):
|
|
483
|
+
"""
|
|
484
|
+
_createReplicationRule_
|
|
485
|
+
|
|
486
|
+
Creates a replication rule against a list of data identifiers.
|
|
487
|
+
:param names: either a string with a did or a list of dids of the same type
|
|
488
|
+
(either a block or a container name)
|
|
489
|
+
:param rseExpression: boolean string expression to give the list of RSEs.
|
|
490
|
+
Full documentation on: https://rucio.readthedocs.io/en/latest/rse_expressions.html
|
|
491
|
+
E.g.: "tier=2&US=true"
|
|
492
|
+
:param scope: string with the scope name
|
|
493
|
+
:param kwargs: supported keyword arguments (from the Rucio CLI API documentation):
|
|
494
|
+
* weight: If the weighting option of the replication rule is used,
|
|
495
|
+
the choice of RSEs takes their weight into account.
|
|
496
|
+
* lifetime: The lifetime of the replication rules (in seconds).
|
|
497
|
+
* grouping: Decides how the replication is going to happen; where:
|
|
498
|
+
ALL: All files will be replicated to the same RSE.
|
|
499
|
+
DATASET: All files in the same block will be replicated to the same RSE.
|
|
500
|
+
NONE: Files will be completely spread over all allowed RSEs without any
|
|
501
|
+
grouping considerations at all.
|
|
502
|
+
E.g.: ALL grouping for 3 blocks against 3 RSEs. All 3 blocks go to one of the RSEs
|
|
503
|
+
DATASET grouping for 3 blocks against 3 RSEs, each block gets fully replicated
|
|
504
|
+
to one of those random RSEs
|
|
505
|
+
* account: The account owning the rule.
|
|
506
|
+
* locked: If the rule is locked, it cannot be deleted.
|
|
507
|
+
* source_replica_expression: RSE Expression for RSEs to be considered for source replicas.
|
|
508
|
+
* activity: Transfer Activity to be passed to FTS.
|
|
509
|
+
* notify: Notification setting for the rule (Y, N, C).
|
|
510
|
+
* purge_replicas: When the rule gets deleted purge the associated replicas immediately.
|
|
511
|
+
* ignore_availability: Option to ignore the availability of RSEs.
|
|
512
|
+
* ask_approval: Ask for approval of this replication rule.
|
|
513
|
+
* asynchronous: Create rule asynchronously by judge-injector.
|
|
514
|
+
* priority: Priority of the transfers.
|
|
515
|
+
* comment: Comment about the rule.
|
|
516
|
+
* meta: Metadata, as dictionary.
|
|
517
|
+
:return: it returns either an empty list or a list with a string id for the rule created
|
|
518
|
+
|
|
519
|
+
NOTE: if there is an AccessDenied rucio exception, it raises a WMRucioException
|
|
520
|
+
"""
|
|
521
|
+
kwargs.setdefault('grouping', 'ALL')
|
|
522
|
+
kwargs.setdefault('account', self.rucioParams.get('account'))
|
|
523
|
+
kwargs.setdefault('lifetime', None)
|
|
524
|
+
kwargs.setdefault('locked', False)
|
|
525
|
+
kwargs.setdefault('notify', 'N')
|
|
526
|
+
kwargs.setdefault('purge_replicas', False)
|
|
527
|
+
kwargs.setdefault('ignore_availability', False)
|
|
528
|
+
kwargs.setdefault('ask_approval', False)
|
|
529
|
+
kwargs.setdefault('asynchronous', True)
|
|
530
|
+
kwargs.setdefault('priority', 3)
|
|
531
|
+
if isinstance(kwargs.get('meta'), dict):
|
|
532
|
+
kwargs['meta'] = json.dumps(kwargs['meta'])
|
|
533
|
+
|
|
534
|
+
if not isinstance(names, (list, set)):
|
|
535
|
+
names = [names]
|
|
536
|
+
dids = [{'scope': scope, 'name': did} for did in names]
|
|
537
|
+
|
|
538
|
+
response = []
|
|
539
|
+
try:
|
|
540
|
+
response = self.cli.add_replication_rule(dids, copies, rseExpression, **kwargs)
|
|
541
|
+
except AccessDenied as ex:
|
|
542
|
+
msg = "AccessDenied creating DID replication rule. Error: %s" % str(ex)
|
|
543
|
+
raise WMRucioException(msg)
|
|
544
|
+
except DuplicateRule as ex:
|
|
545
|
+
# NOTE:
|
|
546
|
+
# The unique constraint is per did and it is checked against the tuple:
|
|
547
|
+
# rucioAccount + did = (name, scope) + rseExpression
|
|
548
|
+
# NOTE:
|
|
549
|
+
# This exception will be thrown by Rucio even if a single Did has
|
|
550
|
+
# a duplicate rule. In this case all the rest of the Dids will be
|
|
551
|
+
# ignored, which in general should be addressed by Rucio. But since
|
|
552
|
+
# it is not, we should break the list of Dids and proceed one by one
|
|
553
|
+
self.logger.warning("Resolving duplicate rules and separating every DID in a new rule...")
|
|
554
|
+
|
|
555
|
+
ruleIds = []
|
|
556
|
+
# now try creating a new rule for every single DID in a separated call
|
|
557
|
+
for did in dids:
|
|
558
|
+
try:
|
|
559
|
+
response = self.cli.add_replication_rule([did], copies, rseExpression, **kwargs)
|
|
560
|
+
ruleIds.extend(response)
|
|
561
|
+
except DuplicateRule:
|
|
562
|
+
self.logger.warning("Found duplicate rule for account: %s\n, rseExp: %s\ndids: %s",
|
|
563
|
+
kwargs['account'], rseExpression, dids)
|
|
564
|
+
# Well, then let us find which rule_id is already in the system
|
|
565
|
+
for rule in self.listDataRules(did['name'], scope=did['scope'],
|
|
566
|
+
account=kwargs['account'], rse_expression=rseExpression):
|
|
567
|
+
ruleIds.append(rule['id'])
|
|
568
|
+
return list(set(ruleIds))
|
|
569
|
+
except Exception as ex:
|
|
570
|
+
self.logger.error("Exception creating rule replica for data: %s. Error: %s", names, str(ex))
|
|
571
|
+
return response
|
|
572
|
+
|
|
573
|
+
def listRuleHistory(self, dids):
|
|
574
|
+
"""
|
|
575
|
+
_listRuleHistory_
|
|
576
|
+
|
|
577
|
+
A function to return a list of historical records of replication rules
|
|
578
|
+
per did.
|
|
579
|
+
:param dids: a list of dids of the form {'name': '...', 'scope: '...'}
|
|
580
|
+
|
|
581
|
+
The returned structure looks something like:
|
|
582
|
+
[{'did': {'name': 'DidName',
|
|
583
|
+
'scope': 'cms'},
|
|
584
|
+
'did_hist': [{u'account': u'wma_test',
|
|
585
|
+
u'created_at': datetime.datetime(2020, 6, 30, 1, 34, 51),
|
|
586
|
+
u'locks_ok_cnt': 9,
|
|
587
|
+
u'locks_replicating_cnt': 0,
|
|
588
|
+
u'locks_stuck_cnt': 0,
|
|
589
|
+
u'rse_expression': u'(tier=2|tier=1)&cms_type=real&rse_type=DISK',
|
|
590
|
+
u'rule_id': u'1f0ab297e4b54e1abf7c086ac012b9e9',
|
|
591
|
+
u'state': u'OK',
|
|
592
|
+
u'updated_at': datetime.datetime(2020, 6, 30, 1, 34, 51)}]},
|
|
593
|
+
...
|
|
594
|
+
{'did': {},
|
|
595
|
+
'did_hist': []}]
|
|
596
|
+
"""
|
|
597
|
+
fullHistory = []
|
|
598
|
+
for did in dids:
|
|
599
|
+
didHistory = {}
|
|
600
|
+
didHistory['did'] = did
|
|
601
|
+
didHistory['did_hist'] = []
|
|
602
|
+
# check the full history of the current did
|
|
603
|
+
for hist in self.cli.list_replication_rule_full_history(did['scope'], did['name']):
|
|
604
|
+
didHistory['did_hist'].append(hist)
|
|
605
|
+
fullHistory.append(didHistory)
|
|
606
|
+
return fullHistory
|
|
607
|
+
|
|
608
|
+
def listContent(self, name, scope='cms'):
|
|
609
|
+
"""
|
|
610
|
+
_listContent_
|
|
611
|
+
|
|
612
|
+
List the content of the data identifier, returning some very basic information.
|
|
613
|
+
:param name: data identifier (either a block or a container name)
|
|
614
|
+
:param scope: string with the scope name
|
|
615
|
+
:return: a list with dictionary items
|
|
616
|
+
"""
|
|
617
|
+
res = []
|
|
618
|
+
try:
|
|
619
|
+
res = self.cli.list_content(scope, name)
|
|
620
|
+
except Exception as ex:
|
|
621
|
+
self.logger.error("Exception listing content of: %s. Error: %s", name, str(ex))
|
|
622
|
+
return list(res)
|
|
623
|
+
|
|
624
|
+
def listDataRules(self, name, **kwargs):
|
|
625
|
+
"""
|
|
626
|
+
_listDataRules_
|
|
627
|
+
|
|
628
|
+
List all rules associated to the data identifier provided.
|
|
629
|
+
:param name: data identifier (either a block or a container name)
|
|
630
|
+
:param kwargs: key/value filters supported by list_replication_rules Rucio API, such as:
|
|
631
|
+
* scope: string with the scope name (optional)
|
|
632
|
+
* account: string with the rucio account name (optional)
|
|
633
|
+
* state: string with the state name (optional)
|
|
634
|
+
* grouping: string with the grouping name (optional)
|
|
635
|
+
* did_type: string with the DID type (optional)
|
|
636
|
+
* created_before: an RFC-1123 compliant date string (optional)
|
|
637
|
+
* created_after: an RFC-1123 compliant date string (optional)
|
|
638
|
+
* updated_before: an RFC-1123 compliant date string (optional)
|
|
639
|
+
* updated_after: an RFC-1123 compliant date string (optional)
|
|
640
|
+
* and any of the other supported query arguments from the ReplicationRule class, see:
|
|
641
|
+
https://github.com/rucio/rucio/blob/master/lib/rucio/db/sqla/models.py#L884
|
|
642
|
+
:return: a list with dictionary items
|
|
643
|
+
"""
|
|
644
|
+
kwargs["name"] = name
|
|
645
|
+
kwargs.setdefault("scope", "cms")
|
|
646
|
+
try:
|
|
647
|
+
res = self.cli.list_replication_rules(kwargs)
|
|
648
|
+
except Exception as exc:
|
|
649
|
+
msg = "Exception listing rules for data: {} and kwargs: {}. Error: {}".format(name,
|
|
650
|
+
kwargs,
|
|
651
|
+
str(exc))
|
|
652
|
+
raise WMRucioException(msg)
|
|
653
|
+
return list(res)
|
|
654
|
+
|
|
655
|
+
def listDataRulesHistory(self, name, scope='cms'):
|
|
656
|
+
"""
|
|
657
|
+
_listDataRulesHistory_
|
|
658
|
+
|
|
659
|
+
List the whole rule history of a given DID.
|
|
660
|
+
:param name: data identifier (either a block or a container name)
|
|
661
|
+
:param scope: string with the scope name
|
|
662
|
+
:return: a list with dictionary items
|
|
663
|
+
"""
|
|
664
|
+
res = []
|
|
665
|
+
try:
|
|
666
|
+
res = self.cli.list_replication_rule_full_history(scope, name)
|
|
667
|
+
except Exception as ex:
|
|
668
|
+
self.logger.error("Exception listing rules history for data: %s. Error: %s", name, str(ex))
|
|
669
|
+
return list(res)
|
|
670
|
+
|
|
671
|
+
def listParentDIDs(self, name, scope='cms'):
|
|
672
|
+
"""
|
|
673
|
+
_listParentDID__
|
|
674
|
+
|
|
675
|
+
List the parent block/container of the specified DID.
|
|
676
|
+
:param name: data identifier (either a block or a file name)
|
|
677
|
+
:param scope: string with the scope name
|
|
678
|
+
:return: a list with dictionary items
|
|
679
|
+
"""
|
|
680
|
+
res = []
|
|
681
|
+
try:
|
|
682
|
+
res = self.cli.list_parent_dids(scope, name)
|
|
683
|
+
except Exception as ex:
|
|
684
|
+
self.logger.error("Exception listing parent DIDs for data: %s. Error: %s", name, str(ex))
|
|
685
|
+
return list(res)
|
|
686
|
+
|
|
687
|
+
def getRule(self, ruleId):
|
|
688
|
+
"""
|
|
689
|
+
_getRule_
|
|
690
|
+
|
|
691
|
+
Retrieve rule information for a given rule id
|
|
692
|
+
:param ruleId: string with the rule id
|
|
693
|
+
:return: a dictionary with the rule data (or empty if no rule can be found)
|
|
694
|
+
"""
|
|
695
|
+
res = {}
|
|
696
|
+
try:
|
|
697
|
+
res = self.cli.get_replication_rule(ruleId)
|
|
698
|
+
except RuleNotFound:
|
|
699
|
+
self.logger.error("Cannot find any information for rule id: %s", ruleId)
|
|
700
|
+
except Exception as ex:
|
|
701
|
+
self.logger.error("Exception getting rule id: %s. Error: %s", ruleId, str(ex))
|
|
702
|
+
return res
|
|
703
|
+
|
|
704
|
+
def updateRule(self, ruleId, opts):
|
|
705
|
+
"""
|
|
706
|
+
Update rule information for a given rule id
|
|
707
|
+
:param ruleId: string with the rule id
|
|
708
|
+
:param opts: dictionary, rule id options passed to Rucio
|
|
709
|
+
:return: boolean status of update call
|
|
710
|
+
"""
|
|
711
|
+
status = None
|
|
712
|
+
try:
|
|
713
|
+
status = self.cli.update_replication_rule(ruleId, opts)
|
|
714
|
+
except RuleNotFound:
|
|
715
|
+
self.logger.error("Cannot find any information for rule id: %s", ruleId)
|
|
716
|
+
except Exception as ex:
|
|
717
|
+
self.logger.error("Exception updating rule id: %s. Error: %s", ruleId, str(ex))
|
|
718
|
+
return status
|
|
719
|
+
|
|
720
|
+
def deleteRule(self, ruleId, purgeReplicas=False):
|
|
721
|
+
"""
|
|
722
|
+
_deleteRule_
|
|
723
|
+
|
|
724
|
+
Deletes a replication rule and all its associated locks
|
|
725
|
+
:param ruleId: string with the rule id
|
|
726
|
+
:param purgeReplicas: bool,ean to immediately delete the replicas
|
|
727
|
+
:return: a boolean to represent whether it succeeded or not
|
|
728
|
+
"""
|
|
729
|
+
res = True
|
|
730
|
+
try:
|
|
731
|
+
res = self.cli.delete_replication_rule(ruleId, purge_replicas=purgeReplicas)
|
|
732
|
+
except RuleNotFound:
|
|
733
|
+
self.logger.error("Could not find rule id: %s. Assuming it has already been deleted", ruleId)
|
|
734
|
+
except Exception as ex:
|
|
735
|
+
self.logger.error("Exception deleting rule id: %s. Error: %s", ruleId, str(ex))
|
|
736
|
+
res = False
|
|
737
|
+
return res
|
|
738
|
+
|
|
739
|
+
def evaluateRSEExpression(self, rseExpr, useCache=True, returnTape=True):
|
|
740
|
+
"""
|
|
741
|
+
Provided an RSE expression, resolve it and return a flat list of RSEs
|
|
742
|
+
:param rseExpr: an RSE expression (which could be the RSE itself...)
|
|
743
|
+
:param useCache: boolean defining whether cached data is meant to be used or not
|
|
744
|
+
:param returnTape: boolean to also return Tape RSEs from the RSE expression result
|
|
745
|
+
:return: a list of RSE names
|
|
746
|
+
"""
|
|
747
|
+
if self.cachedRSEs.isCacheExpired():
|
|
748
|
+
self.cachedRSEs.reset()
|
|
749
|
+
if useCache and rseExpr in self.cachedRSEs:
|
|
750
|
+
if returnTape:
|
|
751
|
+
return self.cachedRSEs[rseExpr]
|
|
752
|
+
return dropTapeRSEs(self.cachedRSEs[rseExpr])
|
|
753
|
+
else:
|
|
754
|
+
matchingRSEs = []
|
|
755
|
+
try:
|
|
756
|
+
for item in self.cli.list_rses(rseExpr):
|
|
757
|
+
matchingRSEs.append(item['rse'])
|
|
758
|
+
except InvalidRSEExpression as exc:
|
|
759
|
+
msg = "Provided RSE expression is considered invalid: {}. Error: {}".format(rseExpr, str(exc))
|
|
760
|
+
raise WMRucioException(msg)
|
|
761
|
+
if useCache:
|
|
762
|
+
# add this key/value pair to the cache
|
|
763
|
+
self.cachedRSEs.addItemToCache({rseExpr: matchingRSEs})
|
|
764
|
+
if returnTape:
|
|
765
|
+
return matchingRSEs
|
|
766
|
+
return dropTapeRSEs(matchingRSEs)
|
|
767
|
+
|
|
768
|
+
def pickRSE(self, rseExpression='rse_type=TAPE\cms_type=test', rseAttribute='dm_weight'):
|
|
769
|
+
"""
|
|
770
|
+
_pickRSE_
|
|
771
|
+
|
|
772
|
+
Use a weighted random selection algorithm to pick an RSE for a dataset based on an RSE attribute.
|
|
773
|
+
The attribute should correlate to space available.
|
|
774
|
+
:param rseExpression: Rucio RSE expression to pick RSEs (defaults to production Tape RSEs)
|
|
775
|
+
:param rseAttribute: The RSE attribute to use as a weight. Must be a number
|
|
776
|
+
|
|
777
|
+
Returns: A tuple of the chosen RSE and if the chosen RSE requires approval to write (rule property)
|
|
778
|
+
"""
|
|
779
|
+
matchingRSEs = self.evaluateRSEExpression(rseExpression)
|
|
780
|
+
rsesWithApproval = []
|
|
781
|
+
rsesWeight = []
|
|
782
|
+
|
|
783
|
+
for rse in matchingRSEs:
|
|
784
|
+
rseAttrs = self.cli.list_rse_attributes(rse)
|
|
785
|
+
if rseAttribute:
|
|
786
|
+
try:
|
|
787
|
+
attrValue = float(rseAttrs.get(rseAttribute, 0))
|
|
788
|
+
except (TypeError, KeyError):
|
|
789
|
+
attrValue = 0
|
|
790
|
+
else:
|
|
791
|
+
attrValue = 1
|
|
792
|
+
requiresApproval = rseAttrs.get('requires_approval', False)
|
|
793
|
+
rsesWithApproval.append((rse, requiresApproval))
|
|
794
|
+
rsesWeight.append(attrValue)
|
|
795
|
+
|
|
796
|
+
return weightedChoice(rsesWithApproval, rsesWeight)
|
|
797
|
+
|
|
798
|
+
def requiresApproval(self, rse):
|
|
799
|
+
"""
|
|
800
|
+
_requiresApproval_
|
|
801
|
+
|
|
802
|
+
Returns wether or not the specified RSE requires rule approval.
|
|
803
|
+
:param res: string containing the Rucio RSE name
|
|
804
|
+
:returns: True if the RSE requires approval to write (rule property)
|
|
805
|
+
"""
|
|
806
|
+
try:
|
|
807
|
+
attrs = self.cli.list_rse_attributes(rse)
|
|
808
|
+
except RSENotFound as exc:
|
|
809
|
+
msg = "Error retrieving attributes for RSE: {}. Error: {}".format(rse, str(exc))
|
|
810
|
+
raise WMRucioException(msg)
|
|
811
|
+
return attrs.get('requires_approval', False)
|
|
812
|
+
|
|
813
|
+
def isContainer(self, didName, scope='cms'):
|
|
814
|
+
"""
|
|
815
|
+
Checks whether the DID name corresponds to a container type or not.
|
|
816
|
+
:param didName: string with the DID name
|
|
817
|
+
:param scope: string containing the Rucio scope (defaults to 'cms')
|
|
818
|
+
:return: True if the DID is a container, else False
|
|
819
|
+
"""
|
|
820
|
+
try:
|
|
821
|
+
response = self.cli.get_did(scope=scope, name=didName)
|
|
822
|
+
except DataIdentifierNotFound as exc:
|
|
823
|
+
msg = "Data identifier not found in Rucio: {}. Error: {}".format(didName, str(exc))
|
|
824
|
+
raise WMRucioDIDNotFoundException(msg)
|
|
825
|
+
return response['type'].upper() == 'CONTAINER'
|
|
826
|
+
|
|
827
|
+
def getDID(self, didName, dynamic=True, scope='cms'):
|
|
828
|
+
"""
|
|
829
|
+
Retrieves basic information for a single data identifier.
|
|
830
|
+
:param didName: string with the DID name
|
|
831
|
+
:param dynamic: boolean to dynamically calculate the DID size (default to True)
|
|
832
|
+
:param scope: string containing the Rucio scope (defaults to 'cms')
|
|
833
|
+
:return: a dictionary with basic DID information
|
|
834
|
+
"""
|
|
835
|
+
try:
|
|
836
|
+
response = self.cli.get_did(scope=scope, name=didName, dynamic=dynamic)
|
|
837
|
+
except DataIdentifierNotFound as exc:
|
|
838
|
+
response = dict()
|
|
839
|
+
self.logger.error("Data identifier not found in Rucio: %s. Error: %s", didName, str(exc))
|
|
840
|
+
return response
|
|
841
|
+
|
|
842
|
+
def didExist(self, didName, scope='cms'):
|
|
843
|
+
"""
|
|
844
|
+
Provided a given DID, check whether it's already in the Rucio server.
|
|
845
|
+
Any kind of exception will return False (thus, data not yet in Rucio).
|
|
846
|
+
:param didName: a string with the DID name (container, block, or file)
|
|
847
|
+
:return: True if DID has been found, False otherwise
|
|
848
|
+
"""
|
|
849
|
+
try:
|
|
850
|
+
response = self.cli.get_did(scope=scope, name=didName)
|
|
851
|
+
except Exception:
|
|
852
|
+
response = dict()
|
|
853
|
+
return response.get("name") == didName
|
|
854
|
+
|
|
855
|
+
# FIXME we can likely delete this method (replaced by another implementation)
|
|
856
|
+
def getDataLockedAndAvailable_old(self, **kwargs):
|
|
857
|
+
"""
|
|
858
|
+
This method retrieves all the locations where a given DID is
|
|
859
|
+
currently available and locked. It can be used for the data
|
|
860
|
+
location logic in global and local workqueue.
|
|
861
|
+
|
|
862
|
+
Logic is as follows:
|
|
863
|
+
1. look for all replication rules matching the provided keyword args
|
|
864
|
+
2. location of single RSE rules and in state OK are set as a location
|
|
865
|
+
3.a. if the input DID name is a block, get all the locks AVAILABLE for that block
|
|
866
|
+
and compare the rule ID against the multi RSE rules. Keep the RSE if it matches
|
|
867
|
+
3.b. otherwise - if it's a container - method `_getContainerLockedAndAvailable`
|
|
868
|
+
gets called to resolve all the blocks and locks
|
|
869
|
+
4. union of the single RSEs and the matched multi RSEs is returned as the
|
|
870
|
+
final location of the provided DID
|
|
871
|
+
|
|
872
|
+
:param kwargs: key/value pairs to filter out the rules. Most common filters are likely:
|
|
873
|
+
scope: string with the scope name
|
|
874
|
+
name: string with the DID name
|
|
875
|
+
account: string with the rucio account name
|
|
876
|
+
state: string with the state name
|
|
877
|
+
grouping: string with the grouping name
|
|
878
|
+
:param returnTape: boolean to return Tape RSEs in the output, if any
|
|
879
|
+
:return: a flat list with the RSE names locking and holding the input DID
|
|
880
|
+
|
|
881
|
+
NOTE: some of the supported values can be looked up at:
|
|
882
|
+
https://github.com/rucio/rucio/blob/master/lib/rucio/db/sqla/constants.py#L184
|
|
883
|
+
"""
|
|
884
|
+
msg = "This method `getDataLockedAndAvailable_old` is getting deprecated "
|
|
885
|
+
msg += "and it will be removed in future releases."
|
|
886
|
+
self.logger.warning(msg)
|
|
887
|
+
returnTape = kwargs.pop("returnTape", False)
|
|
888
|
+
if 'name' not in kwargs:
|
|
889
|
+
raise WMRucioException("A DID name must be provided to the getDataLockedAndAvailable API")
|
|
890
|
+
if 'grouping' in kwargs:
|
|
891
|
+
# long strings seem not to be working, like ALL / DATASET
|
|
892
|
+
if kwargs['grouping'] == "ALL":
|
|
893
|
+
kwargs['grouping'] = "A"
|
|
894
|
+
elif kwargs['grouping'] == "DATASET":
|
|
895
|
+
kwargs['grouping'] = "D"
|
|
896
|
+
|
|
897
|
+
kwargs.setdefault("scope", "cms")
|
|
898
|
+
|
|
899
|
+
multiRSERules = []
|
|
900
|
+
finalRSEs = set()
|
|
901
|
+
|
|
902
|
+
# First, find all the rules and where data is supposed to be locked
|
|
903
|
+
rules = self.cli.list_replication_rules(kwargs)
|
|
904
|
+
for rule in rules:
|
|
905
|
+
# now resolve the RSE expressions
|
|
906
|
+
rses = self.evaluateRSEExpression(rule['rse_expression'], returnTape=returnTape)
|
|
907
|
+
if rule['copies'] == len(rses) and rule['state'] == "OK":
|
|
908
|
+
# then we can guarantee that data is locked and available on these RSEs
|
|
909
|
+
finalRSEs.update(set(rses))
|
|
910
|
+
else:
|
|
911
|
+
multiRSERules.append(rule['id'])
|
|
912
|
+
self.logger.debug("Data location for %s from single RSE locks and available at: %s",
|
|
913
|
+
kwargs['name'], list(finalRSEs))
|
|
914
|
+
if not multiRSERules:
|
|
915
|
+
# then that is it, we can return the current RSEs holding and locking this data
|
|
916
|
+
return list(finalRSEs)
|
|
917
|
+
|
|
918
|
+
# At this point, we might already have some of the RSEs where the data is available and locked
|
|
919
|
+
# Now check dataset locks and compare those rules against our list of multi RSE rules
|
|
920
|
+
if self.isContainer(kwargs['name'], scope=kwargs.get('scope', 'cms')):
|
|
921
|
+
# It's a container! Find what those RSEs are and add them to the finalRSEs set
|
|
922
|
+
rseLocks = self._getContainerLockedAndAvailable(multiRSERules, returnTape=returnTape, **kwargs)
|
|
923
|
+
self.logger.debug("Data location for %s from multiple RSE locks and available at: %s",
|
|
924
|
+
kwargs['name'], list(rseLocks))
|
|
925
|
+
else:
|
|
926
|
+
# It's a single block! Find what those RSEs are and add them to the finalRSEs set
|
|
927
|
+
rseLocks = set()
|
|
928
|
+
for blockLock in self.cli.get_dataset_locks(kwargs['scope'], kwargs['name']):
|
|
929
|
+
if blockLock['state'] == 'OK' and blockLock['rule_id'] in multiRSERules:
|
|
930
|
+
rseLocks.add(blockLock['rse'])
|
|
931
|
+
self.logger.debug("Data location for %s from multiple RSE locks and available at: %s",
|
|
932
|
+
kwargs['name'], list(rseLocks))
|
|
933
|
+
|
|
934
|
+
finalRSEs = list(finalRSEs | rseLocks)
|
|
935
|
+
return finalRSEs
|
|
936
|
+
|
|
937
|
+
# FIXME we can likely delete this method
|
|
938
|
+
def _getContainerLockedAndAvailable_old(self, multiRSERules, **kwargs):
|
|
939
|
+
"""
|
|
940
|
+
This method is only supposed to be called internally (private method),
|
|
941
|
+
because it won't consider the container level rules.
|
|
942
|
+
|
|
943
|
+
This method retrieves all the locations where a given container DID is
|
|
944
|
+
currently available and locked. It can be used for the data
|
|
945
|
+
location logic in global and local workqueue.
|
|
946
|
+
|
|
947
|
+
Logic is as follows:
|
|
948
|
+
1. find all the blocks in the provided container name
|
|
949
|
+
2. loop over all the block names and fetch their locks AVAILABLE
|
|
950
|
+
2.a. compare the rule ID against the multi RSE rules. Keep the RSE if it matches
|
|
951
|
+
3.a. if keyword argument grouping is ALL, returns an intersection of all blocks RSEs
|
|
952
|
+
3.b. else - grouping DATASET - returns an union of all blocks RSEs
|
|
953
|
+
|
|
954
|
+
:param multiRSERules: list of container level rules to be matched against
|
|
955
|
+
:param kwargs: key/value pairs to filter out the rules. Most common filters are likely:
|
|
956
|
+
scope: string with the scope name
|
|
957
|
+
name: string with the DID name
|
|
958
|
+
account: string with the rucio account name
|
|
959
|
+
state: string with the state name
|
|
960
|
+
grouping: string with the grouping name
|
|
961
|
+
:param returnTape: boolean to return Tape RSEs in the output, if any
|
|
962
|
+
:return: a set with the RSE names locking and holding this input DID
|
|
963
|
+
|
|
964
|
+
NOTE: some of the supported values can be looked up at:
|
|
965
|
+
https://github.com/rucio/rucio/blob/master/lib/rucio/db/sqla/constants.py#L184
|
|
966
|
+
"""
|
|
967
|
+
msg = "This method `_getContainerLockedAndAvailable_old` is getting deprecated "
|
|
968
|
+
msg += "and it will be removed in future releases."
|
|
969
|
+
self.logger.warning(msg)
|
|
970
|
+
returnTape = kwargs.pop("returnTape", False)
|
|
971
|
+
finalRSEs = set()
|
|
972
|
+
blockNames = self.getBlocksInContainer(kwargs['name'])
|
|
973
|
+
self.logger.debug("Container: %s contains %d blocks. Querying dataset_locks ...",
|
|
974
|
+
kwargs['name'], len(blockNames))
|
|
975
|
+
|
|
976
|
+
rsesByBlocks = {}
|
|
977
|
+
for block in blockNames:
|
|
978
|
+
rsesByBlocks.setdefault(block, set())
|
|
979
|
+
# FIXME: feature request made to the Rucio team to support bulk operations:
|
|
980
|
+
# https://github.com/rucio/rucio/issues/3982
|
|
981
|
+
for blockLock in self.cli.get_dataset_locks(kwargs['scope'], block):
|
|
982
|
+
if not returnTape and isTapeRSE(blockLock['rse']):
|
|
983
|
+
continue
|
|
984
|
+
if blockLock['state'] == 'OK' and blockLock['rule_id'] in multiRSERules:
|
|
985
|
+
rsesByBlocks[block].add(blockLock['rse'])
|
|
986
|
+
|
|
987
|
+
# The question now is:
|
|
988
|
+
# 1. do we want to have a full copy of the container in the same RSEs (grouping=A)
|
|
989
|
+
# 2. or we want all locations holding at least one block of the container (grouping=D)
|
|
990
|
+
if kwargs.get('grouping') == 'A':
|
|
991
|
+
firstRun = True
|
|
992
|
+
for _block, rses in viewitems(rsesByBlocks):
|
|
993
|
+
if firstRun:
|
|
994
|
+
finalRSEs = rses
|
|
995
|
+
firstRun = False
|
|
996
|
+
else:
|
|
997
|
+
finalRSEs = finalRSEs & rses
|
|
998
|
+
else:
|
|
999
|
+
for _block, rses in viewitems(rsesByBlocks):
|
|
1000
|
+
finalRSEs = finalRSEs | rses
|
|
1001
|
+
return finalRSEs
|
|
1002
|
+
|
|
1003
|
+
def getPileupLockedAndAvailable(self, container, account, scope="cms"):
|
|
1004
|
+
"""
|
|
1005
|
+
Method to resolve where the pileup container (and all its blocks)
|
|
1006
|
+
is locked and available.
|
|
1007
|
+
|
|
1008
|
+
Pileup location resolution involves the following logic:
|
|
1009
|
+
1. find replication rules at the container level
|
|
1010
|
+
* if num of copies is equal to num of rses, and state is Ok, use
|
|
1011
|
+
those RSEs as container location (thus, every single block)
|
|
1012
|
+
* elif there are more rses than copies, keep that rule id for the next step
|
|
1013
|
+
2. discover all the blocks in the container
|
|
1014
|
+
3. if there are no multi RSEs rules, just build the block location map and return
|
|
1015
|
+
3. otherwise, for every block, list their current locks and if they are in state=OK
|
|
1016
|
+
and they belong to one of our multiRSEs rule, use that RSE as block location
|
|
1017
|
+
:param container: string with the container name
|
|
1018
|
+
:param account: string with the account name
|
|
1019
|
+
:param scope: string with the scope name (default is "cms")
|
|
1020
|
+
:return: a flat dictionary where the keys are the block names, and the value is
|
|
1021
|
+
a set with the RSE locations
|
|
1022
|
+
|
|
1023
|
+
NOTE: This is somewhat complex, so I decided to make it more readable with
|
|
1024
|
+
a specific method for this process, even though that adds some code duplication.
|
|
1025
|
+
"""
|
|
1026
|
+
result = dict()
|
|
1027
|
+
if not self.isContainer(container, scope=scope):
|
|
1028
|
+
raise WMRucioException("Pileup location needs to be resolved for a container DID type")
|
|
1029
|
+
|
|
1030
|
+
multiRSERules = []
|
|
1031
|
+
finalRSEs = set()
|
|
1032
|
+
kargs = dict(name=container, account=account, scope=scope)
|
|
1033
|
+
|
|
1034
|
+
# First, find all the rules and where data is supposed to be locked
|
|
1035
|
+
for rule in self.cli.list_replication_rules(kargs):
|
|
1036
|
+
rses = self.evaluateRSEExpression(rule['rse_expression'], returnTape=False)
|
|
1037
|
+
if rses and rule['copies'] == len(rses) and rule['state'] == "OK":
|
|
1038
|
+
# then we can guarantee that data is locked and available on these RSEs
|
|
1039
|
+
finalRSEs.update(set(rses))
|
|
1040
|
+
# it could be that the rule was made against Tape only, so check
|
|
1041
|
+
elif rses:
|
|
1042
|
+
multiRSERules.append(rule['id'])
|
|
1043
|
+
self.logger.info("Pileup container location for %s from single RSE locks at: %s",
|
|
1044
|
+
kargs['name'], list(finalRSEs))
|
|
1045
|
+
|
|
1046
|
+
# Second, find all the blocks in this pileup container and assign the container
|
|
1047
|
+
# level locations to them
|
|
1048
|
+
for blockName in self.getBlocksInContainer(kargs['name']):
|
|
1049
|
+
result.update({blockName: finalRSEs})
|
|
1050
|
+
if not multiRSERules:
|
|
1051
|
+
# then that is it, we can return the current RSEs holding and locking this data
|
|
1052
|
+
return result
|
|
1053
|
+
|
|
1054
|
+
# if we got here, then there is a third step to be done.
|
|
1055
|
+
# List every single block lock and check if the rule belongs to the WMCore system
|
|
1056
|
+
for blockName in result:
|
|
1057
|
+
blockRSEs = set()
|
|
1058
|
+
for blockLock in self.cli.get_dataset_locks(scope, blockName):
|
|
1059
|
+
if isTapeRSE(blockLock['rse']):
|
|
1060
|
+
continue
|
|
1061
|
+
if blockLock['state'] == 'OK' and blockLock['rule_id'] in multiRSERules:
|
|
1062
|
+
blockRSEs.add(blockLock['rse'])
|
|
1063
|
+
result[blockName] = finalRSEs | blockRSEs
|
|
1064
|
+
return result
|
|
1065
|
+
|
|
1066
|
+
def getParentContainerRules(self, **kwargs):
|
|
1067
|
+
"""
|
|
1068
|
+
This method takes a DID - such as a file or block - and it resolves its parent
|
|
1069
|
+
DID(s). Then it loops over all parent DIDs and - according to the filters
|
|
1070
|
+
provided in the kwargs - it lists all their rules.
|
|
1071
|
+
:param kwargs: key/value filters supported by list_replication_rules Rucio API, such as:
|
|
1072
|
+
* name: string with the DID name (mandatory)
|
|
1073
|
+
* scope: string with the scope name (optional)
|
|
1074
|
+
* account: string with the rucio account name (optional)
|
|
1075
|
+
* state: string with the state name (optional)
|
|
1076
|
+
* grouping: string with the grouping name (optional)
|
|
1077
|
+
* did_type: string with the DID type (optional)
|
|
1078
|
+
* created_before: an RFC-1123 compliant date string (optional)
|
|
1079
|
+
* created_after: an RFC-1123 compliant date string (optional)
|
|
1080
|
+
* updated_before: an RFC-1123 compliant date string (optional)
|
|
1081
|
+
* updated_after: an RFC-1123 compliant date string (optional)
|
|
1082
|
+
* and any of the other supported query arguments from the ReplicationRule class, see:
|
|
1083
|
+
https://github.com/rucio/rucio/blob/master/lib/rucio/db/sqla/models.py#L884
|
|
1084
|
+
:return: a list of rule ids made against the parent DIDs
|
|
1085
|
+
"""
|
|
1086
|
+
if 'name' not in kwargs:
|
|
1087
|
+
raise WMRucioException("A DID name must be provided to the getParentContainerLocation API")
|
|
1088
|
+
if 'grouping' in kwargs:
|
|
1089
|
+
# long strings seem not to be working, like ALL / DATASET. Make it short!
|
|
1090
|
+
kwargs['grouping'] = kwargs['grouping'][0]
|
|
1091
|
+
kwargs.setdefault("scope", "cms")
|
|
1092
|
+
didName = kwargs['name']
|
|
1093
|
+
|
|
1094
|
+
listOfRules = []
|
|
1095
|
+
for parentDID in self.listParentDIDs(kwargs['name']):
|
|
1096
|
+
kwargs['name'] = parentDID['name']
|
|
1097
|
+
for rule in self.cli.list_replication_rules(kwargs):
|
|
1098
|
+
listOfRules.append(rule['id'])
|
|
1099
|
+
# revert the original DID name, in case the client will keep using this dict...
|
|
1100
|
+
kwargs['name'] = didName
|
|
1101
|
+
return listOfRules
|
|
1102
|
+
|
|
1103
|
+
def getDataLockedAndAvailable(self, **kwargs):
|
|
1104
|
+
"""
|
|
1105
|
+
This method retrieves all the locations where a given DID is
|
|
1106
|
+
currently available and locked (note that, by default, it will not
|
|
1107
|
+
return any Tape RSEs). The logic is as follows:
|
|
1108
|
+
1. if DID is a container, then return the result from `getContainerLockedAndAvailable`
|
|
1109
|
+
2. resolve the parent DID(s), if any
|
|
1110
|
+
3. list all the replication rule ids for the parent DID(s), if any
|
|
1111
|
+
4. then lists all the replication rules for this specific DID
|
|
1112
|
+
5. then check where blocks are locked and available (state=OK), matching
|
|
1113
|
+
one of the replication rule ids discovered in the previous steps
|
|
1114
|
+
:param kwargs: key/value filters supported by list_replication_rules Rucio API, such as:
|
|
1115
|
+
* name: string with the DID name (mandatory)
|
|
1116
|
+
* scope: string with the scope name (optional)
|
|
1117
|
+
* account: string with the rucio account name (optional)
|
|
1118
|
+
* state: string with the state name (optional)
|
|
1119
|
+
* grouping: string with the grouping name (optional)
|
|
1120
|
+
* did_type: string with the DID type (optional)
|
|
1121
|
+
* created_before: an RFC-1123 compliant date string (optional)
|
|
1122
|
+
* created_after: an RFC-1123 compliant date string (optional)
|
|
1123
|
+
* updated_before: an RFC-1123 compliant date string (optional)
|
|
1124
|
+
* updated_after: an RFC-1123 compliant date string (optional)
|
|
1125
|
+
* and any of the other supported query arguments from the ReplicationRule class, see:
|
|
1126
|
+
https://github.com/rucio/rucio/blob/master/lib/rucio/db/sqla/models.py#L884
|
|
1127
|
+
:param returnTape: boolean to return Tape RSEs in the output, if any
|
|
1128
|
+
:return: a flat list with the RSE names locking and holding the input DID
|
|
1129
|
+
|
|
1130
|
+
NOTE: some of the supported values can be looked up at:
|
|
1131
|
+
https://github.com/rucio/rucio/blob/master/lib/rucio/db/sqla/constants.py#L184
|
|
1132
|
+
"""
|
|
1133
|
+
if 'name' not in kwargs:
|
|
1134
|
+
raise WMRucioException("A DID name must be provided to the getBlockLockedAndAvailable API")
|
|
1135
|
+
if self.isContainer(kwargs['name'], scope=kwargs.get('scope', 'cms')):
|
|
1136
|
+
# then resolve it at container level and all its blocks
|
|
1137
|
+
return self.getContainerLockedAndAvailable(**kwargs)
|
|
1138
|
+
|
|
1139
|
+
if 'grouping' in kwargs:
|
|
1140
|
+
# long strings seem not to be working, like ALL / DATASET. Make it short!
|
|
1141
|
+
kwargs['grouping'] = kwargs['grouping'][0]
|
|
1142
|
+
kwargs.setdefault("scope", "cms")
|
|
1143
|
+
returnTape = kwargs.pop("returnTape", False)
|
|
1144
|
+
|
|
1145
|
+
finalRSEs = set()
|
|
1146
|
+
# first, fetch the rules locking the - possible - parent DIDs
|
|
1147
|
+
allRuleIds = self.getParentContainerRules(**kwargs)
|
|
1148
|
+
|
|
1149
|
+
# then lists all the rules for this specific DID
|
|
1150
|
+
for rule in self.cli.list_replication_rules(kwargs):
|
|
1151
|
+
allRuleIds.append(rule['id'])
|
|
1152
|
+
|
|
1153
|
+
# now with all the rules in hands, we can start checking block locks
|
|
1154
|
+
for blockLock in self.cli.get_dataset_locks(kwargs['scope'], kwargs['name']):
|
|
1155
|
+
if blockLock['state'] == 'OK' and blockLock['rule_id'] in allRuleIds:
|
|
1156
|
+
finalRSEs.add(blockLock['rse'])
|
|
1157
|
+
if not returnTape:
|
|
1158
|
+
finalRSEs = dropTapeRSEs(finalRSEs)
|
|
1159
|
+
else:
|
|
1160
|
+
finalRSEs = list(finalRSEs)
|
|
1161
|
+
return finalRSEs
|
|
1162
|
+
|
|
1163
|
+
def getContainerLockedAndAvailable(self, **kwargs):
|
|
1164
|
+
"""
|
|
1165
|
+
This method retrieves all the locations where a given container DID is
|
|
1166
|
+
currently available and locked (note that, by default, it will not
|
|
1167
|
+
return any Tape RSEs). The logic is as follows:
|
|
1168
|
+
1. for each container-level replication rule, check
|
|
1169
|
+
i. if the rule state=OK and if the number of copies is equals to
|
|
1170
|
+
the number of RSEs. If so, that is one of the container locations
|
|
1171
|
+
ii. elif the rule state=OK, then consider that rule id (and its RSEs) to
|
|
1172
|
+
be evaluated against the dataset locks
|
|
1173
|
+
iii. otherwise, don't use the container rule
|
|
1174
|
+
2. if there were no rules with multiple RSEs, then return the RSEs from step 1
|
|
1175
|
+
3. else, retrieve all the blocks in the container and build a block-based dictionary
|
|
1176
|
+
by listing all the dataset_locks for all the blocks:
|
|
1177
|
+
i. if the lock state=OK and the rule_id belongs to one of those multiple RSE locks
|
|
1178
|
+
from step-1, then use that block location
|
|
1179
|
+
ii. else, the location can't be used
|
|
1180
|
+
5. finally, build the final container location based on the input `grouping` parameter
|
|
1181
|
+
specified, such as:
|
|
1182
|
+
i. grouping=ALL triggers an intersection (AND) of all blocks location, which gets added
|
|
1183
|
+
to the already discovered container-level location
|
|
1184
|
+
ii. other grouping values (like DATASET) triggers an union of all blocks location (note
|
|
1185
|
+
that it only takes one block location to consider it as a final location), and a final
|
|
1186
|
+
union of this list with the container-level location is made
|
|
1187
|
+
:param kwargs: key/value filters supported by list_replication_rules Rucio API, such as:
|
|
1188
|
+
* name: string with the DID name (mandatory)
|
|
1189
|
+
* scope: string with the scope name (optional)
|
|
1190
|
+
* account: string with the rucio account name (optional)
|
|
1191
|
+
* state: string with the state name (optional)
|
|
1192
|
+
* grouping: string with the grouping name (optional)
|
|
1193
|
+
* did_type: string with the DID type (optional)
|
|
1194
|
+
* created_before: an RFC-1123 compliant date string (optional)
|
|
1195
|
+
* created_after: an RFC-1123 compliant date string (optional)
|
|
1196
|
+
* updated_before: an RFC-1123 compliant date string (optional)
|
|
1197
|
+
* updated_after: an RFC-1123 compliant date string (optional)
|
|
1198
|
+
* and any of the other supported query arguments from the ReplicationRule class, see:
|
|
1199
|
+
https://github.com/rucio/rucio/blob/master/lib/rucio/db/sqla/models.py#L884
|
|
1200
|
+
:param returnTape: boolean to return Tape RSEs in the output, if any
|
|
1201
|
+
:return: a flat list with the RSE names locking and holding the input DID
|
|
1202
|
+
|
|
1203
|
+
NOTE-1: this is not a full scan of data locking and availability because it does
|
|
1204
|
+
not list the replication rules for blocks!!!
|
|
1205
|
+
|
|
1206
|
+
NOTE-2: some of the supported values can be looked up at:
|
|
1207
|
+
https://github.com/rucio/rucio/blob/master/lib/rucio/db/sqla/constants.py#L184
|
|
1208
|
+
"""
|
|
1209
|
+
if 'name' not in kwargs:
|
|
1210
|
+
raise WMRucioException("A DID name must be provided to the getContainerLockedAndAvailable API")
|
|
1211
|
+
if 'grouping' in kwargs:
|
|
1212
|
+
# long strings seem not to be working, like ALL / DATASET. Make it short!
|
|
1213
|
+
kwargs['grouping'] = kwargs['grouping'][0]
|
|
1214
|
+
kwargs.setdefault("scope", "cms")
|
|
1215
|
+
returnTape = kwargs.pop("returnTape", False)
|
|
1216
|
+
|
|
1217
|
+
finalRSEs = set()
|
|
1218
|
+
multiRSERules = []
|
|
1219
|
+
# first, find all the rules locking this container matching the kwargs
|
|
1220
|
+
for rule in self.cli.list_replication_rules(kwargs):
|
|
1221
|
+
# now resolve the RSE expressions
|
|
1222
|
+
rses = self.evaluateRSEExpression(rule['rse_expression'])
|
|
1223
|
+
if rule['copies'] == len(rses) and rule['state'] == "OK":
|
|
1224
|
+
# then we can guarantee that data is locked and available on these RSEs
|
|
1225
|
+
finalRSEs.update(set(rses))
|
|
1226
|
+
elif rule['state'] == "OK":
|
|
1227
|
+
if returnTape:
|
|
1228
|
+
multiRSERules.append(rule['id'])
|
|
1229
|
+
elif dropTapeRSEs(rses):
|
|
1230
|
+
# if it's not a tape-only rule, use it
|
|
1231
|
+
multiRSERules.append(rule['id'])
|
|
1232
|
+
else:
|
|
1233
|
+
self.logger.debug("Container rule: %s not yet satisfied. State: %s", rule['id'], rule['state'])
|
|
1234
|
+
self.logger.info("Container: %s with container-based location at: %s",
|
|
1235
|
+
kwargs['name'], finalRSEs)
|
|
1236
|
+
if not multiRSERules:
|
|
1237
|
+
return list(finalRSEs)
|
|
1238
|
+
|
|
1239
|
+
# second, find all the blocks in this container and loop over all of them,
|
|
1240
|
+
# checking where they are locked and available
|
|
1241
|
+
locationByBlock = dict()
|
|
1242
|
+
for block in self.getBlocksInContainer(kwargs['name']):
|
|
1243
|
+
locationByBlock.setdefault(block, set())
|
|
1244
|
+
for blockLock in self.cli.get_dataset_locks(kwargs['scope'], block):
|
|
1245
|
+
if blockLock['state'] == 'OK' and blockLock['rule_id'] in multiRSERules:
|
|
1246
|
+
locationByBlock[block].add(blockLock['rse'])
|
|
1247
|
+
|
|
1248
|
+
# lastly, the final list of RSEs will depend on data grouping requested
|
|
1249
|
+
# ALL --> container location is an intersection of each block location
|
|
1250
|
+
# DATASET --> container location is the union of each block location.
|
|
1251
|
+
# Note that a block without any location will not affect the final result.
|
|
1252
|
+
commonBlockRSEs = set()
|
|
1253
|
+
if kwargs.get('grouping') == 'A':
|
|
1254
|
+
firstRun = True
|
|
1255
|
+
for rses in viewvalues(locationByBlock):
|
|
1256
|
+
if firstRun:
|
|
1257
|
+
commonBlockRSEs = set(rses)
|
|
1258
|
+
firstRun = False
|
|
1259
|
+
else:
|
|
1260
|
+
commonBlockRSEs = commonBlockRSEs & set(rses)
|
|
1261
|
+
# finally, append the block based location to the container rule location
|
|
1262
|
+
finalRSEs.update(commonBlockRSEs)
|
|
1263
|
+
else:
|
|
1264
|
+
for rses in viewvalues(locationByBlock):
|
|
1265
|
+
commonBlockRSEs = commonBlockRSEs | set(rses)
|
|
1266
|
+
finalRSEs = finalRSEs | commonBlockRSEs
|
|
1267
|
+
|
|
1268
|
+
if not returnTape:
|
|
1269
|
+
finalRSEs = dropTapeRSEs(finalRSEs)
|
|
1270
|
+
else:
|
|
1271
|
+
finalRSEs = list(finalRSEs)
|
|
1272
|
+
self.logger.info("Container: %s with block-based location at: %s, and final location: %s",
|
|
1273
|
+
kwargs['name'], commonBlockRSEs, finalRSEs)
|
|
1274
|
+
return finalRSEs
|
|
1275
|
+
|
|
1276
|
+
def getRSEUsage(self, rse):
|
|
1277
|
+
"""
|
|
1278
|
+
get_rse_usage rucio API
|
|
1279
|
+
|
|
1280
|
+
:param rse: name of RSE
|
|
1281
|
+
:return: generator of records about given RSE
|
|
1282
|
+
"""
|
|
1283
|
+
try:
|
|
1284
|
+
return self.cli.get_rse_usage(rse)
|
|
1285
|
+
except Exception as exp:
|
|
1286
|
+
self.logger.error("Failed to get information about rse %s. Error: %s", rse, str(exp))
|
|
1287
|
+
raise exp
|