wmglobalqueue 2.4.5.1__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.
Files changed (347) hide show
  1. Utils/CPMetrics.py +270 -0
  2. Utils/CertTools.py +100 -0
  3. Utils/EmailAlert.py +50 -0
  4. Utils/ExtendedUnitTestCase.py +62 -0
  5. Utils/FileTools.py +182 -0
  6. Utils/IteratorTools.py +80 -0
  7. Utils/MathUtils.py +31 -0
  8. Utils/MemoryCache.py +119 -0
  9. Utils/Patterns.py +24 -0
  10. Utils/Pipeline.py +137 -0
  11. Utils/PortForward.py +97 -0
  12. Utils/ProcFS.py +112 -0
  13. Utils/ProcessStats.py +194 -0
  14. Utils/PythonVersion.py +17 -0
  15. Utils/Signals.py +36 -0
  16. Utils/TemporaryEnvironment.py +27 -0
  17. Utils/Throttled.py +227 -0
  18. Utils/Timers.py +130 -0
  19. Utils/Timestamps.py +86 -0
  20. Utils/TokenManager.py +143 -0
  21. Utils/Tracing.py +60 -0
  22. Utils/TwPrint.py +98 -0
  23. Utils/Utilities.py +318 -0
  24. Utils/__init__.py +11 -0
  25. Utils/wmcoreDTools.py +707 -0
  26. WMCore/ACDC/Collection.py +57 -0
  27. WMCore/ACDC/CollectionTypes.py +12 -0
  28. WMCore/ACDC/CouchCollection.py +67 -0
  29. WMCore/ACDC/CouchFileset.py +238 -0
  30. WMCore/ACDC/CouchService.py +73 -0
  31. WMCore/ACDC/DataCollectionService.py +485 -0
  32. WMCore/ACDC/Fileset.py +94 -0
  33. WMCore/ACDC/__init__.py +11 -0
  34. WMCore/Algorithms/Alarm.py +39 -0
  35. WMCore/Algorithms/MathAlgos.py +274 -0
  36. WMCore/Algorithms/MiscAlgos.py +67 -0
  37. WMCore/Algorithms/ParseXMLFile.py +115 -0
  38. WMCore/Algorithms/Permissions.py +27 -0
  39. WMCore/Algorithms/Singleton.py +58 -0
  40. WMCore/Algorithms/SubprocessAlgos.py +129 -0
  41. WMCore/Algorithms/__init__.py +7 -0
  42. WMCore/Cache/GenericDataCache.py +98 -0
  43. WMCore/Cache/WMConfigCache.py +572 -0
  44. WMCore/Cache/__init__.py +0 -0
  45. WMCore/Configuration.py +659 -0
  46. WMCore/DAOFactory.py +47 -0
  47. WMCore/DataStructs/File.py +177 -0
  48. WMCore/DataStructs/Fileset.py +140 -0
  49. WMCore/DataStructs/Job.py +182 -0
  50. WMCore/DataStructs/JobGroup.py +142 -0
  51. WMCore/DataStructs/JobPackage.py +49 -0
  52. WMCore/DataStructs/LumiList.py +734 -0
  53. WMCore/DataStructs/Mask.py +219 -0
  54. WMCore/DataStructs/MathStructs/ContinuousSummaryHistogram.py +197 -0
  55. WMCore/DataStructs/MathStructs/DiscreteSummaryHistogram.py +92 -0
  56. WMCore/DataStructs/MathStructs/SummaryHistogram.py +117 -0
  57. WMCore/DataStructs/MathStructs/__init__.py +0 -0
  58. WMCore/DataStructs/Pickleable.py +24 -0
  59. WMCore/DataStructs/Run.py +256 -0
  60. WMCore/DataStructs/Subscription.py +175 -0
  61. WMCore/DataStructs/WMObject.py +47 -0
  62. WMCore/DataStructs/WorkUnit.py +112 -0
  63. WMCore/DataStructs/Workflow.py +60 -0
  64. WMCore/DataStructs/__init__.py +8 -0
  65. WMCore/Database/CMSCouch.py +1430 -0
  66. WMCore/Database/ConfigDBMap.py +29 -0
  67. WMCore/Database/CouchMonitoring.py +450 -0
  68. WMCore/Database/CouchUtils.py +118 -0
  69. WMCore/Database/DBCore.py +198 -0
  70. WMCore/Database/DBCreator.py +113 -0
  71. WMCore/Database/DBExceptionHandler.py +59 -0
  72. WMCore/Database/DBFactory.py +117 -0
  73. WMCore/Database/DBFormatter.py +177 -0
  74. WMCore/Database/Dialects.py +13 -0
  75. WMCore/Database/ExecuteDAO.py +327 -0
  76. WMCore/Database/MongoDB.py +241 -0
  77. WMCore/Database/MySQL/Destroy.py +42 -0
  78. WMCore/Database/MySQL/ListUserContent.py +20 -0
  79. WMCore/Database/MySQL/__init__.py +9 -0
  80. WMCore/Database/MySQLCore.py +132 -0
  81. WMCore/Database/Oracle/Destroy.py +56 -0
  82. WMCore/Database/Oracle/ListUserContent.py +19 -0
  83. WMCore/Database/Oracle/__init__.py +9 -0
  84. WMCore/Database/ResultSet.py +44 -0
  85. WMCore/Database/Transaction.py +91 -0
  86. WMCore/Database/__init__.py +9 -0
  87. WMCore/Database/ipy_profile_couch.py +438 -0
  88. WMCore/GlobalWorkQueue/CherryPyThreads/CleanUpTask.py +29 -0
  89. WMCore/GlobalWorkQueue/CherryPyThreads/HeartbeatMonitor.py +105 -0
  90. WMCore/GlobalWorkQueue/CherryPyThreads/LocationUpdateTask.py +28 -0
  91. WMCore/GlobalWorkQueue/CherryPyThreads/ReqMgrInteractionTask.py +35 -0
  92. WMCore/GlobalWorkQueue/CherryPyThreads/__init__.py +0 -0
  93. WMCore/GlobalWorkQueue/__init__.py +0 -0
  94. WMCore/GroupUser/CouchObject.py +127 -0
  95. WMCore/GroupUser/Decorators.py +51 -0
  96. WMCore/GroupUser/Group.py +33 -0
  97. WMCore/GroupUser/Interface.py +73 -0
  98. WMCore/GroupUser/User.py +96 -0
  99. WMCore/GroupUser/__init__.py +11 -0
  100. WMCore/Lexicon.py +836 -0
  101. WMCore/REST/Auth.py +202 -0
  102. WMCore/REST/CherryPyPeriodicTask.py +166 -0
  103. WMCore/REST/Error.py +333 -0
  104. WMCore/REST/Format.py +642 -0
  105. WMCore/REST/HeartbeatMonitorBase.py +90 -0
  106. WMCore/REST/Main.py +636 -0
  107. WMCore/REST/Server.py +2435 -0
  108. WMCore/REST/Services.py +24 -0
  109. WMCore/REST/Test.py +120 -0
  110. WMCore/REST/Tools.py +38 -0
  111. WMCore/REST/Validation.py +250 -0
  112. WMCore/REST/__init__.py +1 -0
  113. WMCore/ReqMgr/DataStructs/RequestStatus.py +209 -0
  114. WMCore/ReqMgr/DataStructs/RequestType.py +13 -0
  115. WMCore/ReqMgr/DataStructs/__init__.py +0 -0
  116. WMCore/ReqMgr/__init__.py +1 -0
  117. WMCore/Services/AlertManager/AlertManagerAPI.py +111 -0
  118. WMCore/Services/AlertManager/__init__.py +0 -0
  119. WMCore/Services/CRIC/CRIC.py +238 -0
  120. WMCore/Services/CRIC/__init__.py +0 -0
  121. WMCore/Services/DBS/DBS3Reader.py +1044 -0
  122. WMCore/Services/DBS/DBSConcurrency.py +44 -0
  123. WMCore/Services/DBS/DBSErrors.py +112 -0
  124. WMCore/Services/DBS/DBSReader.py +23 -0
  125. WMCore/Services/DBS/DBSUtils.py +166 -0
  126. WMCore/Services/DBS/DBSWriterObjects.py +381 -0
  127. WMCore/Services/DBS/ProdException.py +133 -0
  128. WMCore/Services/DBS/__init__.py +8 -0
  129. WMCore/Services/FWJRDB/FWJRDBAPI.py +118 -0
  130. WMCore/Services/FWJRDB/__init__.py +0 -0
  131. WMCore/Services/HTTPS/HTTPSAuthHandler.py +66 -0
  132. WMCore/Services/HTTPS/__init__.py +0 -0
  133. WMCore/Services/LogDB/LogDB.py +201 -0
  134. WMCore/Services/LogDB/LogDBBackend.py +191 -0
  135. WMCore/Services/LogDB/LogDBExceptions.py +11 -0
  136. WMCore/Services/LogDB/LogDBReport.py +85 -0
  137. WMCore/Services/LogDB/__init__.py +0 -0
  138. WMCore/Services/MSPileup/__init__.py +0 -0
  139. WMCore/Services/MSUtils/MSUtils.py +54 -0
  140. WMCore/Services/MSUtils/__init__.py +0 -0
  141. WMCore/Services/McM/McM.py +173 -0
  142. WMCore/Services/McM/__init__.py +8 -0
  143. WMCore/Services/MonIT/Grafana.py +133 -0
  144. WMCore/Services/MonIT/__init__.py +0 -0
  145. WMCore/Services/PyCondor/PyCondorAPI.py +154 -0
  146. WMCore/Services/PyCondor/__init__.py +0 -0
  147. WMCore/Services/ReqMgr/ReqMgr.py +261 -0
  148. WMCore/Services/ReqMgr/__init__.py +0 -0
  149. WMCore/Services/ReqMgrAux/ReqMgrAux.py +419 -0
  150. WMCore/Services/ReqMgrAux/__init__.py +0 -0
  151. WMCore/Services/RequestDB/RequestDBReader.py +267 -0
  152. WMCore/Services/RequestDB/RequestDBWriter.py +39 -0
  153. WMCore/Services/RequestDB/__init__.py +0 -0
  154. WMCore/Services/Requests.py +624 -0
  155. WMCore/Services/Rucio/Rucio.py +1290 -0
  156. WMCore/Services/Rucio/RucioUtils.py +74 -0
  157. WMCore/Services/Rucio/__init__.py +0 -0
  158. WMCore/Services/RucioConMon/RucioConMon.py +121 -0
  159. WMCore/Services/RucioConMon/__init__.py +0 -0
  160. WMCore/Services/Service.py +400 -0
  161. WMCore/Services/StompAMQ/__init__.py +0 -0
  162. WMCore/Services/TagCollector/TagCollector.py +155 -0
  163. WMCore/Services/TagCollector/XMLUtils.py +98 -0
  164. WMCore/Services/TagCollector/__init__.py +0 -0
  165. WMCore/Services/UUIDLib.py +13 -0
  166. WMCore/Services/UserFileCache/UserFileCache.py +160 -0
  167. WMCore/Services/UserFileCache/__init__.py +8 -0
  168. WMCore/Services/WMAgent/WMAgent.py +63 -0
  169. WMCore/Services/WMAgent/__init__.py +0 -0
  170. WMCore/Services/WMArchive/CMSSWMetrics.py +526 -0
  171. WMCore/Services/WMArchive/DataMap.py +463 -0
  172. WMCore/Services/WMArchive/WMArchive.py +33 -0
  173. WMCore/Services/WMArchive/__init__.py +0 -0
  174. WMCore/Services/WMBS/WMBS.py +97 -0
  175. WMCore/Services/WMBS/__init__.py +0 -0
  176. WMCore/Services/WMStats/DataStruct/RequestInfoCollection.py +300 -0
  177. WMCore/Services/WMStats/DataStruct/__init__.py +0 -0
  178. WMCore/Services/WMStats/WMStatsPycurl.py +145 -0
  179. WMCore/Services/WMStats/WMStatsReader.py +445 -0
  180. WMCore/Services/WMStats/WMStatsWriter.py +273 -0
  181. WMCore/Services/WMStats/__init__.py +0 -0
  182. WMCore/Services/WMStatsServer/WMStatsServer.py +134 -0
  183. WMCore/Services/WMStatsServer/__init__.py +0 -0
  184. WMCore/Services/WorkQueue/WorkQueue.py +492 -0
  185. WMCore/Services/WorkQueue/__init__.py +0 -0
  186. WMCore/Services/__init__.py +8 -0
  187. WMCore/Services/pycurl_manager.py +574 -0
  188. WMCore/WMBase.py +50 -0
  189. WMCore/WMConnectionBase.py +164 -0
  190. WMCore/WMException.py +183 -0
  191. WMCore/WMExceptions.py +269 -0
  192. WMCore/WMFactory.py +76 -0
  193. WMCore/WMInit.py +377 -0
  194. WMCore/WMLogging.py +104 -0
  195. WMCore/WMSpec/ConfigSectionTree.py +442 -0
  196. WMCore/WMSpec/Persistency.py +135 -0
  197. WMCore/WMSpec/Steps/BuildMaster.py +87 -0
  198. WMCore/WMSpec/Steps/BuildTools.py +201 -0
  199. WMCore/WMSpec/Steps/Builder.py +97 -0
  200. WMCore/WMSpec/Steps/Diagnostic.py +89 -0
  201. WMCore/WMSpec/Steps/Emulator.py +62 -0
  202. WMCore/WMSpec/Steps/ExecuteMaster.py +208 -0
  203. WMCore/WMSpec/Steps/Executor.py +210 -0
  204. WMCore/WMSpec/Steps/StepFactory.py +213 -0
  205. WMCore/WMSpec/Steps/TaskEmulator.py +75 -0
  206. WMCore/WMSpec/Steps/Template.py +204 -0
  207. WMCore/WMSpec/Steps/Templates/AlcaHarvest.py +76 -0
  208. WMCore/WMSpec/Steps/Templates/CMSSW.py +613 -0
  209. WMCore/WMSpec/Steps/Templates/DQMUpload.py +59 -0
  210. WMCore/WMSpec/Steps/Templates/DeleteFiles.py +70 -0
  211. WMCore/WMSpec/Steps/Templates/LogArchive.py +84 -0
  212. WMCore/WMSpec/Steps/Templates/LogCollect.py +105 -0
  213. WMCore/WMSpec/Steps/Templates/StageOut.py +105 -0
  214. WMCore/WMSpec/Steps/Templates/__init__.py +10 -0
  215. WMCore/WMSpec/Steps/WMExecutionFailure.py +21 -0
  216. WMCore/WMSpec/Steps/__init__.py +8 -0
  217. WMCore/WMSpec/Utilities.py +63 -0
  218. WMCore/WMSpec/WMSpecErrors.py +12 -0
  219. WMCore/WMSpec/WMStep.py +347 -0
  220. WMCore/WMSpec/WMTask.py +1997 -0
  221. WMCore/WMSpec/WMWorkload.py +2288 -0
  222. WMCore/WMSpec/WMWorkloadTools.py +382 -0
  223. WMCore/WMSpec/__init__.py +9 -0
  224. WMCore/WorkQueue/DataLocationMapper.py +273 -0
  225. WMCore/WorkQueue/DataStructs/ACDCBlock.py +47 -0
  226. WMCore/WorkQueue/DataStructs/Block.py +48 -0
  227. WMCore/WorkQueue/DataStructs/CouchWorkQueueElement.py +148 -0
  228. WMCore/WorkQueue/DataStructs/WorkQueueElement.py +274 -0
  229. WMCore/WorkQueue/DataStructs/WorkQueueElementResult.py +152 -0
  230. WMCore/WorkQueue/DataStructs/WorkQueueElementsSummary.py +185 -0
  231. WMCore/WorkQueue/DataStructs/__init__.py +0 -0
  232. WMCore/WorkQueue/Policy/End/EndPolicyInterface.py +44 -0
  233. WMCore/WorkQueue/Policy/End/SingleShot.py +22 -0
  234. WMCore/WorkQueue/Policy/End/__init__.py +32 -0
  235. WMCore/WorkQueue/Policy/PolicyInterface.py +17 -0
  236. WMCore/WorkQueue/Policy/Start/Block.py +258 -0
  237. WMCore/WorkQueue/Policy/Start/Dataset.py +180 -0
  238. WMCore/WorkQueue/Policy/Start/MonteCarlo.py +131 -0
  239. WMCore/WorkQueue/Policy/Start/ResubmitBlock.py +171 -0
  240. WMCore/WorkQueue/Policy/Start/StartPolicyInterface.py +316 -0
  241. WMCore/WorkQueue/Policy/Start/__init__.py +34 -0
  242. WMCore/WorkQueue/Policy/__init__.py +57 -0
  243. WMCore/WorkQueue/WMBSHelper.py +772 -0
  244. WMCore/WorkQueue/WorkQueue.py +1237 -0
  245. WMCore/WorkQueue/WorkQueueBackend.py +750 -0
  246. WMCore/WorkQueue/WorkQueueBase.py +39 -0
  247. WMCore/WorkQueue/WorkQueueExceptions.py +44 -0
  248. WMCore/WorkQueue/WorkQueueReqMgrInterface.py +278 -0
  249. WMCore/WorkQueue/WorkQueueUtils.py +130 -0
  250. WMCore/WorkQueue/__init__.py +13 -0
  251. WMCore/Wrappers/JsonWrapper/JSONThunker.py +342 -0
  252. WMCore/Wrappers/JsonWrapper/__init__.py +7 -0
  253. WMCore/Wrappers/__init__.py +6 -0
  254. WMCore/__init__.py +10 -0
  255. wmglobalqueue-2.4.5.1.data/data/bin/wmc-dist-patch +15 -0
  256. wmglobalqueue-2.4.5.1.data/data/bin/wmc-dist-unpatch +8 -0
  257. wmglobalqueue-2.4.5.1.data/data/bin/wmc-httpd +3 -0
  258. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/.couchapprc +1 -0
  259. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/README.md +40 -0
  260. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/_attachments/index.html +264 -0
  261. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/_attachments/js/ElementInfoByWorkflow.js +96 -0
  262. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/_attachments/js/StuckElementInfo.js +57 -0
  263. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/_attachments/js/WorkloadInfoTable.js +80 -0
  264. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/_attachments/js/dataTable.js +70 -0
  265. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/_attachments/js/namespace.js +23 -0
  266. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/_attachments/style/main.css +75 -0
  267. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/couchapp.json +4 -0
  268. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/filters/childQueueFilter.js +13 -0
  269. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/filters/filterDeletedDocs.js +3 -0
  270. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/filters/queueFilter.js +11 -0
  271. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/language +1 -0
  272. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/lib/mustache.js +333 -0
  273. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/lib/validate.js +27 -0
  274. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/lib/workqueue_utils.js +61 -0
  275. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/lists/elementsDetail.js +28 -0
  276. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/lists/filter.js +86 -0
  277. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/lists/stuckElements.js +38 -0
  278. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/lists/workRestrictions.js +153 -0
  279. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/lists/workflowSummary.js +28 -0
  280. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/rewrites.json +73 -0
  281. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/shows/redirect.js +23 -0
  282. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/shows/status.js +40 -0
  283. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/templates/ElementSummaryByWorkflow.html +27 -0
  284. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/templates/StuckElementSummary.html +26 -0
  285. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/templates/TaskStatus.html +23 -0
  286. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/templates/WorkflowSummary.html +27 -0
  287. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/templates/partials/workqueue-common-lib.html +2 -0
  288. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/templates/partials/yui-lib-remote.html +16 -0
  289. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/templates/partials/yui-lib.html +18 -0
  290. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/updates/in-place.js +50 -0
  291. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/validate_doc_update.js +8 -0
  292. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/vendor/couchapp/_attachments/jquery.couch.app.js +235 -0
  293. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/vendor/couchapp/_attachments/jquery.pathbinder.js +173 -0
  294. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/activeData/map.js +8 -0
  295. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/activeData/reduce.js +2 -0
  296. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/activeParentData/map.js +8 -0
  297. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/activeParentData/reduce.js +2 -0
  298. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/activePileupData/map.js +8 -0
  299. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/activePileupData/reduce.js +2 -0
  300. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/analyticsData/map.js +11 -0
  301. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/analyticsData/reduce.js +1 -0
  302. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/availableByPriority/map.js +6 -0
  303. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/conflicts/map.js +5 -0
  304. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/elements/map.js +5 -0
  305. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/elementsByData/map.js +8 -0
  306. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/elementsByParent/map.js +8 -0
  307. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/elementsByParentData/map.js +8 -0
  308. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/elementsByPileupData/map.js +8 -0
  309. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/elementsByStatus/map.js +8 -0
  310. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/elementsBySubscription/map.js +6 -0
  311. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/elementsByWorkflow/map.js +8 -0
  312. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/elementsByWorkflow/reduce.js +3 -0
  313. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/elementsDetailByWorkflowAndStatus/map.js +26 -0
  314. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/jobInjectStatusByRequest/map.js +10 -0
  315. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/jobInjectStatusByRequest/reduce.js +1 -0
  316. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/jobStatusByRequest/map.js +6 -0
  317. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/jobStatusByRequest/reduce.js +1 -0
  318. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/jobsByChildQueueAndPriority/map.js +6 -0
  319. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/jobsByChildQueueAndPriority/reduce.js +1 -0
  320. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/jobsByChildQueueAndStatus/map.js +6 -0
  321. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/jobsByChildQueueAndStatus/reduce.js +1 -0
  322. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/jobsByRequest/map.js +6 -0
  323. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/jobsByRequest/reduce.js +1 -0
  324. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/jobsByStatus/map.js +6 -0
  325. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/jobsByStatus/reduce.js +1 -0
  326. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/jobsByStatusAndPriority/map.js +6 -0
  327. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/jobsByStatusAndPriority/reduce.js +1 -0
  328. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/openRequests/map.js +6 -0
  329. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/recent-items/map.js +5 -0
  330. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/siteWhitelistByRequest/map.js +6 -0
  331. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/siteWhitelistByRequest/reduce.js +1 -0
  332. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/specsByWorkflow/map.js +5 -0
  333. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/stuckElements/map.js +38 -0
  334. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/wmbsInjectStatusByRequest/map.js +12 -0
  335. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/wmbsInjectStatusByRequest/reduce.js +3 -0
  336. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/wmbsUrl/map.js +6 -0
  337. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/wmbsUrl/reduce.js +2 -0
  338. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/wmbsUrlByRequest/map.js +6 -0
  339. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/wmbsUrlByRequest/reduce.js +2 -0
  340. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/workflowSummary/map.js +9 -0
  341. wmglobalqueue-2.4.5.1.data/data/data/couchapps/WorkQueue/views/workflowSummary/reduce.js +10 -0
  342. wmglobalqueue-2.4.5.1.dist-info/METADATA +26 -0
  343. wmglobalqueue-2.4.5.1.dist-info/RECORD +347 -0
  344. wmglobalqueue-2.4.5.1.dist-info/WHEEL +5 -0
  345. wmglobalqueue-2.4.5.1.dist-info/licenses/LICENSE +202 -0
  346. wmglobalqueue-2.4.5.1.dist-info/licenses/NOTICE +16 -0
  347. wmglobalqueue-2.4.5.1.dist-info/top_level.txt +2 -0
@@ -0,0 +1,1290 @@
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 to represent whether it succeeded or not.
710
+ ok status code and RuleNotFound exception are considered as succeeded,
711
+ any other Exception case is considered as failed.
712
+ """
713
+ status = True
714
+ try:
715
+ status = self.cli.update_replication_rule(ruleId, opts)
716
+ except RuleNotFound:
717
+ self.logger.error("Cannot find any information for rule id: %s", ruleId)
718
+ except Exception as ex:
719
+ self.logger.error("Exception updating rule id: %s. Error: %s", ruleId, str(ex))
720
+ status = False
721
+ return status
722
+
723
+ def deleteRule(self, ruleId, purgeReplicas=False):
724
+ """
725
+ _deleteRule_
726
+
727
+ Deletes a replication rule and all its associated locks
728
+ :param ruleId: string with the rule id
729
+ :param purgeReplicas: bool,ean to immediately delete the replicas
730
+ :return: a boolean to represent whether it succeeded or not
731
+ """
732
+ res = True
733
+ try:
734
+ res = self.cli.delete_replication_rule(ruleId, purge_replicas=purgeReplicas)
735
+ except RuleNotFound:
736
+ self.logger.error("Could not find rule id: %s. Assuming it has already been deleted", ruleId)
737
+ except Exception as ex:
738
+ self.logger.error("Exception deleting rule id: %s. Error: %s", ruleId, str(ex))
739
+ res = False
740
+ return res
741
+
742
+ def evaluateRSEExpression(self, rseExpr, useCache=True, returnTape=True):
743
+ """
744
+ Provided an RSE expression, resolve it and return a flat list of RSEs
745
+ :param rseExpr: an RSE expression (which could be the RSE itself...)
746
+ :param useCache: boolean defining whether cached data is meant to be used or not
747
+ :param returnTape: boolean to also return Tape RSEs from the RSE expression result
748
+ :return: a list of RSE names
749
+ """
750
+ if self.cachedRSEs.isCacheExpired():
751
+ self.cachedRSEs.reset()
752
+ if useCache and rseExpr in self.cachedRSEs:
753
+ if returnTape:
754
+ return self.cachedRSEs[rseExpr]
755
+ return dropTapeRSEs(self.cachedRSEs[rseExpr])
756
+ else:
757
+ matchingRSEs = []
758
+ try:
759
+ for item in self.cli.list_rses(rseExpr):
760
+ matchingRSEs.append(item['rse'])
761
+ except InvalidRSEExpression as exc:
762
+ msg = "Provided RSE expression is considered invalid: {}. Error: {}".format(rseExpr, str(exc))
763
+ raise WMRucioException(msg)
764
+ if useCache:
765
+ # add this key/value pair to the cache
766
+ self.cachedRSEs.addItemToCache({rseExpr: matchingRSEs})
767
+ if returnTape:
768
+ return matchingRSEs
769
+ return dropTapeRSEs(matchingRSEs)
770
+
771
+ def pickRSE(self, rseExpression='rse_type=TAPE\cms_type=test', rseAttribute='dm_weight'):
772
+ """
773
+ _pickRSE_
774
+
775
+ Use a weighted random selection algorithm to pick an RSE for a dataset based on an RSE attribute.
776
+ The attribute should correlate to space available.
777
+ :param rseExpression: Rucio RSE expression to pick RSEs (defaults to production Tape RSEs)
778
+ :param rseAttribute: The RSE attribute to use as a weight. Must be a number
779
+
780
+ Returns: A tuple of the chosen RSE and if the chosen RSE requires approval to write (rule property)
781
+ """
782
+ matchingRSEs = self.evaluateRSEExpression(rseExpression)
783
+ rsesWithApproval = []
784
+ rsesWeight = []
785
+
786
+ for rse in matchingRSEs:
787
+ rseAttrs = self.cli.list_rse_attributes(rse)
788
+ if rseAttribute:
789
+ try:
790
+ attrValue = float(rseAttrs.get(rseAttribute, 0))
791
+ except (TypeError, KeyError):
792
+ attrValue = 0
793
+ else:
794
+ attrValue = 1
795
+ requiresApproval = rseAttrs.get('requires_approval', False)
796
+ rsesWithApproval.append((rse, requiresApproval))
797
+ rsesWeight.append(attrValue)
798
+
799
+ return weightedChoice(rsesWithApproval, rsesWeight)
800
+
801
+ def requiresApproval(self, rse):
802
+ """
803
+ _requiresApproval_
804
+
805
+ Returns wether or not the specified RSE requires rule approval.
806
+ :param res: string containing the Rucio RSE name
807
+ :returns: True if the RSE requires approval to write (rule property)
808
+ """
809
+ try:
810
+ attrs = self.cli.list_rse_attributes(rse)
811
+ except RSENotFound as exc:
812
+ msg = "Error retrieving attributes for RSE: {}. Error: {}".format(rse, str(exc))
813
+ raise WMRucioException(msg)
814
+ return attrs.get('requires_approval', False)
815
+
816
+ def isContainer(self, didName, scope='cms'):
817
+ """
818
+ Checks whether the DID name corresponds to a container type or not.
819
+ :param didName: string with the DID name
820
+ :param scope: string containing the Rucio scope (defaults to 'cms')
821
+ :return: True if the DID is a container, else False
822
+ """
823
+ try:
824
+ response = self.cli.get_did(scope=scope, name=didName)
825
+ except DataIdentifierNotFound as exc:
826
+ msg = "Data identifier not found in Rucio: {}. Error: {}".format(didName, str(exc))
827
+ raise WMRucioDIDNotFoundException(msg)
828
+ return response['type'].upper() == 'CONTAINER'
829
+
830
+ def getDID(self, didName, dynamic=True, scope='cms'):
831
+ """
832
+ Retrieves basic information for a single data identifier.
833
+ :param didName: string with the DID name
834
+ :param dynamic: boolean to dynamically calculate the DID size (default to True)
835
+ :param scope: string containing the Rucio scope (defaults to 'cms')
836
+ :return: a dictionary with basic DID information
837
+ """
838
+ try:
839
+ response = self.cli.get_did(scope=scope, name=didName, dynamic=dynamic)
840
+ except DataIdentifierNotFound as exc:
841
+ response = dict()
842
+ self.logger.error("Data identifier not found in Rucio: %s. Error: %s", didName, str(exc))
843
+ return response
844
+
845
+ def didExist(self, didName, scope='cms'):
846
+ """
847
+ Provided a given DID, check whether it's already in the Rucio server.
848
+ Any kind of exception will return False (thus, data not yet in Rucio).
849
+ :param didName: a string with the DID name (container, block, or file)
850
+ :return: True if DID has been found, False otherwise
851
+ """
852
+ try:
853
+ response = self.cli.get_did(scope=scope, name=didName)
854
+ except Exception:
855
+ response = dict()
856
+ return response.get("name") == didName
857
+
858
+ # FIXME we can likely delete this method (replaced by another implementation)
859
+ def getDataLockedAndAvailable_old(self, **kwargs):
860
+ """
861
+ This method retrieves all the locations where a given DID is
862
+ currently available and locked. It can be used for the data
863
+ location logic in global and local workqueue.
864
+
865
+ Logic is as follows:
866
+ 1. look for all replication rules matching the provided keyword args
867
+ 2. location of single RSE rules and in state OK are set as a location
868
+ 3.a. if the input DID name is a block, get all the locks AVAILABLE for that block
869
+ and compare the rule ID against the multi RSE rules. Keep the RSE if it matches
870
+ 3.b. otherwise - if it's a container - method `_getContainerLockedAndAvailable`
871
+ gets called to resolve all the blocks and locks
872
+ 4. union of the single RSEs and the matched multi RSEs is returned as the
873
+ final location of the provided DID
874
+
875
+ :param kwargs: key/value pairs to filter out the rules. Most common filters are likely:
876
+ scope: string with the scope name
877
+ name: string with the DID name
878
+ account: string with the rucio account name
879
+ state: string with the state name
880
+ grouping: string with the grouping name
881
+ :param returnTape: boolean to return Tape RSEs in the output, if any
882
+ :return: a flat list with the RSE names locking and holding the input DID
883
+
884
+ NOTE: some of the supported values can be looked up at:
885
+ https://github.com/rucio/rucio/blob/master/lib/rucio/db/sqla/constants.py#L184
886
+ """
887
+ msg = "This method `getDataLockedAndAvailable_old` is getting deprecated "
888
+ msg += "and it will be removed in future releases."
889
+ self.logger.warning(msg)
890
+ returnTape = kwargs.pop("returnTape", False)
891
+ if 'name' not in kwargs:
892
+ raise WMRucioException("A DID name must be provided to the getDataLockedAndAvailable API")
893
+ if 'grouping' in kwargs:
894
+ # long strings seem not to be working, like ALL / DATASET
895
+ if kwargs['grouping'] == "ALL":
896
+ kwargs['grouping'] = "A"
897
+ elif kwargs['grouping'] == "DATASET":
898
+ kwargs['grouping'] = "D"
899
+
900
+ kwargs.setdefault("scope", "cms")
901
+
902
+ multiRSERules = []
903
+ finalRSEs = set()
904
+
905
+ # First, find all the rules and where data is supposed to be locked
906
+ rules = self.cli.list_replication_rules(kwargs)
907
+ for rule in rules:
908
+ # now resolve the RSE expressions
909
+ rses = self.evaluateRSEExpression(rule['rse_expression'], returnTape=returnTape)
910
+ if rule['copies'] == len(rses) and rule['state'] == "OK":
911
+ # then we can guarantee that data is locked and available on these RSEs
912
+ finalRSEs.update(set(rses))
913
+ else:
914
+ multiRSERules.append(rule['id'])
915
+ self.logger.debug("Data location for %s from single RSE locks and available at: %s",
916
+ kwargs['name'], list(finalRSEs))
917
+ if not multiRSERules:
918
+ # then that is it, we can return the current RSEs holding and locking this data
919
+ return list(finalRSEs)
920
+
921
+ # At this point, we might already have some of the RSEs where the data is available and locked
922
+ # Now check dataset locks and compare those rules against our list of multi RSE rules
923
+ if self.isContainer(kwargs['name'], scope=kwargs.get('scope', 'cms')):
924
+ # It's a container! Find what those RSEs are and add them to the finalRSEs set
925
+ rseLocks = self._getContainerLockedAndAvailable(multiRSERules, returnTape=returnTape, **kwargs)
926
+ self.logger.debug("Data location for %s from multiple RSE locks and available at: %s",
927
+ kwargs['name'], list(rseLocks))
928
+ else:
929
+ # It's a single block! Find what those RSEs are and add them to the finalRSEs set
930
+ rseLocks = set()
931
+ for blockLock in self.cli.get_dataset_locks(kwargs['scope'], kwargs['name']):
932
+ if blockLock['state'] == 'OK' and blockLock['rule_id'] in multiRSERules:
933
+ rseLocks.add(blockLock['rse'])
934
+ self.logger.debug("Data location for %s from multiple RSE locks and available at: %s",
935
+ kwargs['name'], list(rseLocks))
936
+
937
+ finalRSEs = list(finalRSEs | rseLocks)
938
+ return finalRSEs
939
+
940
+ # FIXME we can likely delete this method
941
+ def _getContainerLockedAndAvailable_old(self, multiRSERules, **kwargs):
942
+ """
943
+ This method is only supposed to be called internally (private method),
944
+ because it won't consider the container level rules.
945
+
946
+ This method retrieves all the locations where a given container DID is
947
+ currently available and locked. It can be used for the data
948
+ location logic in global and local workqueue.
949
+
950
+ Logic is as follows:
951
+ 1. find all the blocks in the provided container name
952
+ 2. loop over all the block names and fetch their locks AVAILABLE
953
+ 2.a. compare the rule ID against the multi RSE rules. Keep the RSE if it matches
954
+ 3.a. if keyword argument grouping is ALL, returns an intersection of all blocks RSEs
955
+ 3.b. else - grouping DATASET - returns an union of all blocks RSEs
956
+
957
+ :param multiRSERules: list of container level rules to be matched against
958
+ :param kwargs: key/value pairs to filter out the rules. Most common filters are likely:
959
+ scope: string with the scope name
960
+ name: string with the DID name
961
+ account: string with the rucio account name
962
+ state: string with the state name
963
+ grouping: string with the grouping name
964
+ :param returnTape: boolean to return Tape RSEs in the output, if any
965
+ :return: a set with the RSE names locking and holding this input DID
966
+
967
+ NOTE: some of the supported values can be looked up at:
968
+ https://github.com/rucio/rucio/blob/master/lib/rucio/db/sqla/constants.py#L184
969
+ """
970
+ msg = "This method `_getContainerLockedAndAvailable_old` is getting deprecated "
971
+ msg += "and it will be removed in future releases."
972
+ self.logger.warning(msg)
973
+ returnTape = kwargs.pop("returnTape", False)
974
+ finalRSEs = set()
975
+ blockNames = self.getBlocksInContainer(kwargs['name'])
976
+ self.logger.debug("Container: %s contains %d blocks. Querying dataset_locks ...",
977
+ kwargs['name'], len(blockNames))
978
+
979
+ rsesByBlocks = {}
980
+ for block in blockNames:
981
+ rsesByBlocks.setdefault(block, set())
982
+ # FIXME: feature request made to the Rucio team to support bulk operations:
983
+ # https://github.com/rucio/rucio/issues/3982
984
+ for blockLock in self.cli.get_dataset_locks(kwargs['scope'], block):
985
+ if not returnTape and isTapeRSE(blockLock['rse']):
986
+ continue
987
+ if blockLock['state'] == 'OK' and blockLock['rule_id'] in multiRSERules:
988
+ rsesByBlocks[block].add(blockLock['rse'])
989
+
990
+ # The question now is:
991
+ # 1. do we want to have a full copy of the container in the same RSEs (grouping=A)
992
+ # 2. or we want all locations holding at least one block of the container (grouping=D)
993
+ if kwargs.get('grouping') == 'A':
994
+ firstRun = True
995
+ for _block, rses in viewitems(rsesByBlocks):
996
+ if firstRun:
997
+ finalRSEs = rses
998
+ firstRun = False
999
+ else:
1000
+ finalRSEs = finalRSEs & rses
1001
+ else:
1002
+ for _block, rses in viewitems(rsesByBlocks):
1003
+ finalRSEs = finalRSEs | rses
1004
+ return finalRSEs
1005
+
1006
+ def getPileupLockedAndAvailable(self, container, account, scope="cms"):
1007
+ """
1008
+ Method to resolve where the pileup container (and all its blocks)
1009
+ is locked and available.
1010
+
1011
+ Pileup location resolution involves the following logic:
1012
+ 1. find replication rules at the container level
1013
+ * if num of copies is equal to num of rses, and state is Ok, use
1014
+ those RSEs as container location (thus, every single block)
1015
+ * elif there are more rses than copies, keep that rule id for the next step
1016
+ 2. discover all the blocks in the container
1017
+ 3. if there are no multi RSEs rules, just build the block location map and return
1018
+ 3. otherwise, for every block, list their current locks and if they are in state=OK
1019
+ and they belong to one of our multiRSEs rule, use that RSE as block location
1020
+ :param container: string with the container name
1021
+ :param account: string with the account name
1022
+ :param scope: string with the scope name (default is "cms")
1023
+ :return: a flat dictionary where the keys are the block names, and the value is
1024
+ a set with the RSE locations
1025
+
1026
+ NOTE: This is somewhat complex, so I decided to make it more readable with
1027
+ a specific method for this process, even though that adds some code duplication.
1028
+ """
1029
+ result = dict()
1030
+ if not self.isContainer(container, scope=scope):
1031
+ raise WMRucioException("Pileup location needs to be resolved for a container DID type")
1032
+
1033
+ multiRSERules = []
1034
+ finalRSEs = set()
1035
+ kargs = dict(name=container, account=account, scope=scope)
1036
+
1037
+ # First, find all the rules and where data is supposed to be locked
1038
+ for rule in self.cli.list_replication_rules(kargs):
1039
+ rses = self.evaluateRSEExpression(rule['rse_expression'], returnTape=False)
1040
+ if rses and rule['copies'] == len(rses) and rule['state'] == "OK":
1041
+ # then we can guarantee that data is locked and available on these RSEs
1042
+ finalRSEs.update(set(rses))
1043
+ # it could be that the rule was made against Tape only, so check
1044
+ elif rses:
1045
+ multiRSERules.append(rule['id'])
1046
+ self.logger.info("Pileup container location for %s from single RSE locks at: %s",
1047
+ kargs['name'], list(finalRSEs))
1048
+
1049
+ # Second, find all the blocks in this pileup container and assign the container
1050
+ # level locations to them
1051
+ for blockName in self.getBlocksInContainer(kargs['name']):
1052
+ result.update({blockName: finalRSEs})
1053
+ if not multiRSERules:
1054
+ # then that is it, we can return the current RSEs holding and locking this data
1055
+ return result
1056
+
1057
+ # if we got here, then there is a third step to be done.
1058
+ # List every single block lock and check if the rule belongs to the WMCore system
1059
+ for blockName in result:
1060
+ blockRSEs = set()
1061
+ for blockLock in self.cli.get_dataset_locks(scope, blockName):
1062
+ if isTapeRSE(blockLock['rse']):
1063
+ continue
1064
+ if blockLock['state'] == 'OK' and blockLock['rule_id'] in multiRSERules:
1065
+ blockRSEs.add(blockLock['rse'])
1066
+ result[blockName] = finalRSEs | blockRSEs
1067
+ return result
1068
+
1069
+ def getParentContainerRules(self, **kwargs):
1070
+ """
1071
+ This method takes a DID - such as a file or block - and it resolves its parent
1072
+ DID(s). Then it loops over all parent DIDs and - according to the filters
1073
+ provided in the kwargs - it lists all their rules.
1074
+ :param kwargs: key/value filters supported by list_replication_rules Rucio API, such as:
1075
+ * name: string with the DID name (mandatory)
1076
+ * scope: string with the scope name (optional)
1077
+ * account: string with the rucio account name (optional)
1078
+ * state: string with the state name (optional)
1079
+ * grouping: string with the grouping name (optional)
1080
+ * did_type: string with the DID type (optional)
1081
+ * created_before: an RFC-1123 compliant date string (optional)
1082
+ * created_after: an RFC-1123 compliant date string (optional)
1083
+ * updated_before: an RFC-1123 compliant date string (optional)
1084
+ * updated_after: an RFC-1123 compliant date string (optional)
1085
+ * and any of the other supported query arguments from the ReplicationRule class, see:
1086
+ https://github.com/rucio/rucio/blob/master/lib/rucio/db/sqla/models.py#L884
1087
+ :return: a list of rule ids made against the parent DIDs
1088
+ """
1089
+ if 'name' not in kwargs:
1090
+ raise WMRucioException("A DID name must be provided to the getParentContainerLocation API")
1091
+ if 'grouping' in kwargs:
1092
+ # long strings seem not to be working, like ALL / DATASET. Make it short!
1093
+ kwargs['grouping'] = kwargs['grouping'][0]
1094
+ kwargs.setdefault("scope", "cms")
1095
+ didName = kwargs['name']
1096
+
1097
+ listOfRules = []
1098
+ for parentDID in self.listParentDIDs(kwargs['name']):
1099
+ kwargs['name'] = parentDID['name']
1100
+ for rule in self.cli.list_replication_rules(kwargs):
1101
+ listOfRules.append(rule['id'])
1102
+ # revert the original DID name, in case the client will keep using this dict...
1103
+ kwargs['name'] = didName
1104
+ return listOfRules
1105
+
1106
+ def getDataLockedAndAvailable(self, **kwargs):
1107
+ """
1108
+ This method retrieves all the locations where a given DID is
1109
+ currently available and locked (note that, by default, it will not
1110
+ return any Tape RSEs). The logic is as follows:
1111
+ 1. if DID is a container, then return the result from `getContainerLockedAndAvailable`
1112
+ 2. resolve the parent DID(s), if any
1113
+ 3. list all the replication rule ids for the parent DID(s), if any
1114
+ 4. then lists all the replication rules for this specific DID
1115
+ 5. then check where blocks are locked and available (state=OK), matching
1116
+ one of the replication rule ids discovered in the previous steps
1117
+ :param kwargs: key/value filters supported by list_replication_rules Rucio API, such as:
1118
+ * name: string with the DID name (mandatory)
1119
+ * scope: string with the scope name (optional)
1120
+ * account: string with the rucio account name (optional)
1121
+ * state: string with the state name (optional)
1122
+ * grouping: string with the grouping name (optional)
1123
+ * did_type: string with the DID type (optional)
1124
+ * created_before: an RFC-1123 compliant date string (optional)
1125
+ * created_after: an RFC-1123 compliant date string (optional)
1126
+ * updated_before: an RFC-1123 compliant date string (optional)
1127
+ * updated_after: an RFC-1123 compliant date string (optional)
1128
+ * and any of the other supported query arguments from the ReplicationRule class, see:
1129
+ https://github.com/rucio/rucio/blob/master/lib/rucio/db/sqla/models.py#L884
1130
+ :param returnTape: boolean to return Tape RSEs in the output, if any
1131
+ :return: a flat list with the RSE names locking and holding the input DID
1132
+
1133
+ NOTE: some of the supported values can be looked up at:
1134
+ https://github.com/rucio/rucio/blob/master/lib/rucio/db/sqla/constants.py#L184
1135
+ """
1136
+ if 'name' not in kwargs:
1137
+ raise WMRucioException("A DID name must be provided to the getBlockLockedAndAvailable API")
1138
+ if self.isContainer(kwargs['name'], scope=kwargs.get('scope', 'cms')):
1139
+ # then resolve it at container level and all its blocks
1140
+ return self.getContainerLockedAndAvailable(**kwargs)
1141
+
1142
+ if 'grouping' in kwargs:
1143
+ # long strings seem not to be working, like ALL / DATASET. Make it short!
1144
+ kwargs['grouping'] = kwargs['grouping'][0]
1145
+ kwargs.setdefault("scope", "cms")
1146
+ returnTape = kwargs.pop("returnTape", False)
1147
+
1148
+ finalRSEs = set()
1149
+ # first, fetch the rules locking the - possible - parent DIDs
1150
+ allRuleIds = self.getParentContainerRules(**kwargs)
1151
+
1152
+ # then lists all the rules for this specific DID
1153
+ for rule in self.cli.list_replication_rules(kwargs):
1154
+ allRuleIds.append(rule['id'])
1155
+
1156
+ # now with all the rules in hands, we can start checking block locks
1157
+ for blockLock in self.cli.get_dataset_locks(kwargs['scope'], kwargs['name']):
1158
+ if blockLock['state'] == 'OK' and blockLock['rule_id'] in allRuleIds:
1159
+ finalRSEs.add(blockLock['rse'])
1160
+ if not returnTape:
1161
+ finalRSEs = dropTapeRSEs(finalRSEs)
1162
+ else:
1163
+ finalRSEs = list(finalRSEs)
1164
+ return finalRSEs
1165
+
1166
+ def getContainerLockedAndAvailable(self, **kwargs):
1167
+ """
1168
+ This method retrieves all the locations where a given container DID is
1169
+ currently available and locked (note that, by default, it will not
1170
+ return any Tape RSEs). The logic is as follows:
1171
+ 1. for each container-level replication rule, check
1172
+ i. if the rule state=OK and if the number of copies is equals to
1173
+ the number of RSEs. If so, that is one of the container locations
1174
+ ii. elif the rule state=OK, then consider that rule id (and its RSEs) to
1175
+ be evaluated against the dataset locks
1176
+ iii. otherwise, don't use the container rule
1177
+ 2. if there were no rules with multiple RSEs, then return the RSEs from step 1
1178
+ 3. else, retrieve all the blocks in the container and build a block-based dictionary
1179
+ by listing all the dataset_locks for all the blocks:
1180
+ i. if the lock state=OK and the rule_id belongs to one of those multiple RSE locks
1181
+ from step-1, then use that block location
1182
+ ii. else, the location can't be used
1183
+ 5. finally, build the final container location based on the input `grouping` parameter
1184
+ specified, such as:
1185
+ i. grouping=ALL triggers an intersection (AND) of all blocks location, which gets added
1186
+ to the already discovered container-level location
1187
+ ii. other grouping values (like DATASET) triggers an union of all blocks location (note
1188
+ that it only takes one block location to consider it as a final location), and a final
1189
+ union of this list with the container-level location is made
1190
+ :param kwargs: key/value filters supported by list_replication_rules Rucio API, such as:
1191
+ * name: string with the DID name (mandatory)
1192
+ * scope: string with the scope name (optional)
1193
+ * account: string with the rucio account name (optional)
1194
+ * state: string with the state name (optional)
1195
+ * grouping: string with the grouping name (optional)
1196
+ * did_type: string with the DID type (optional)
1197
+ * created_before: an RFC-1123 compliant date string (optional)
1198
+ * created_after: an RFC-1123 compliant date string (optional)
1199
+ * updated_before: an RFC-1123 compliant date string (optional)
1200
+ * updated_after: an RFC-1123 compliant date string (optional)
1201
+ * and any of the other supported query arguments from the ReplicationRule class, see:
1202
+ https://github.com/rucio/rucio/blob/master/lib/rucio/db/sqla/models.py#L884
1203
+ :param returnTape: boolean to return Tape RSEs in the output, if any
1204
+ :return: a flat list with the RSE names locking and holding the input DID
1205
+
1206
+ NOTE-1: this is not a full scan of data locking and availability because it does
1207
+ not list the replication rules for blocks!!!
1208
+
1209
+ NOTE-2: some of the supported values can be looked up at:
1210
+ https://github.com/rucio/rucio/blob/master/lib/rucio/db/sqla/constants.py#L184
1211
+ """
1212
+ if 'name' not in kwargs:
1213
+ raise WMRucioException("A DID name must be provided to the getContainerLockedAndAvailable API")
1214
+ if 'grouping' in kwargs:
1215
+ # long strings seem not to be working, like ALL / DATASET. Make it short!
1216
+ kwargs['grouping'] = kwargs['grouping'][0]
1217
+ kwargs.setdefault("scope", "cms")
1218
+ returnTape = kwargs.pop("returnTape", False)
1219
+
1220
+ finalRSEs = set()
1221
+ multiRSERules = []
1222
+ # first, find all the rules locking this container matching the kwargs
1223
+ for rule in self.cli.list_replication_rules(kwargs):
1224
+ # now resolve the RSE expressions
1225
+ rses = self.evaluateRSEExpression(rule['rse_expression'])
1226
+ if rule['copies'] == len(rses) and rule['state'] == "OK":
1227
+ # then we can guarantee that data is locked and available on these RSEs
1228
+ finalRSEs.update(set(rses))
1229
+ elif rule['state'] == "OK":
1230
+ if returnTape:
1231
+ multiRSERules.append(rule['id'])
1232
+ elif dropTapeRSEs(rses):
1233
+ # if it's not a tape-only rule, use it
1234
+ multiRSERules.append(rule['id'])
1235
+ else:
1236
+ self.logger.debug("Container rule: %s not yet satisfied. State: %s", rule['id'], rule['state'])
1237
+ self.logger.info("Container: %s with container-based location at: %s",
1238
+ kwargs['name'], finalRSEs)
1239
+ if not multiRSERules:
1240
+ return list(finalRSEs)
1241
+
1242
+ # second, find all the blocks in this container and loop over all of them,
1243
+ # checking where they are locked and available
1244
+ locationByBlock = dict()
1245
+ for block in self.getBlocksInContainer(kwargs['name']):
1246
+ locationByBlock.setdefault(block, set())
1247
+ for blockLock in self.cli.get_dataset_locks(kwargs['scope'], block):
1248
+ if blockLock['state'] == 'OK' and blockLock['rule_id'] in multiRSERules:
1249
+ locationByBlock[block].add(blockLock['rse'])
1250
+
1251
+ # lastly, the final list of RSEs will depend on data grouping requested
1252
+ # ALL --> container location is an intersection of each block location
1253
+ # DATASET --> container location is the union of each block location.
1254
+ # Note that a block without any location will not affect the final result.
1255
+ commonBlockRSEs = set()
1256
+ if kwargs.get('grouping') == 'A':
1257
+ firstRun = True
1258
+ for rses in viewvalues(locationByBlock):
1259
+ if firstRun:
1260
+ commonBlockRSEs = set(rses)
1261
+ firstRun = False
1262
+ else:
1263
+ commonBlockRSEs = commonBlockRSEs & set(rses)
1264
+ # finally, append the block based location to the container rule location
1265
+ finalRSEs.update(commonBlockRSEs)
1266
+ else:
1267
+ for rses in viewvalues(locationByBlock):
1268
+ commonBlockRSEs = commonBlockRSEs | set(rses)
1269
+ finalRSEs = finalRSEs | commonBlockRSEs
1270
+
1271
+ if not returnTape:
1272
+ finalRSEs = dropTapeRSEs(finalRSEs)
1273
+ else:
1274
+ finalRSEs = list(finalRSEs)
1275
+ self.logger.info("Container: %s with block-based location at: %s, and final location: %s",
1276
+ kwargs['name'], commonBlockRSEs, finalRSEs)
1277
+ return finalRSEs
1278
+
1279
+ def getRSEUsage(self, rse):
1280
+ """
1281
+ get_rse_usage rucio API
1282
+
1283
+ :param rse: name of RSE
1284
+ :return: generator of records about given RSE
1285
+ """
1286
+ try:
1287
+ return self.cli.get_rse_usage(rse)
1288
+ except Exception as exp:
1289
+ self.logger.error("Failed to get information about rse %s. Error: %s", rse, str(exp))
1290
+ raise exp