rucio-clients 35.6.0__tar.gz → 36.0.0rc2__tar.gz
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 rucio-clients might be problematic. Click here for more details.
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/PKG-INFO +1 -1
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/bin/rucio +2 -1
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/etc/rucio.cfg.atlas.client.template +3 -2
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/etc/rucio.cfg.template +2 -1
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/client/baseclient.py +7 -5
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/client/downloadclient.py +3 -1
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/client/rseclient.py +4 -4
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/client/uploadclient.py +4 -1
- rucio_clients-36.0.0rc2/lib/rucio/common/bittorrent.py +234 -0
- rucio_clients-36.0.0rc2/lib/rucio/common/cache.py +111 -0
- rucio_clients-36.0.0rc2/lib/rucio/common/checksum.py +151 -0
- rucio_clients-36.0.0rc2/lib/rucio/common/client.py +122 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/common/config.py +10 -15
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/common/constants.py +60 -2
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/common/didtype.py +72 -24
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/common/exception.py +23 -5
- rucio_clients-36.0.0rc2/lib/rucio/common/extra.py +31 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/common/logging.py +13 -13
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/common/plugins.py +59 -27
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/common/schema/__init__.py +51 -9
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/common/types.py +108 -50
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/common/utils.py +63 -630
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/rse/__init__.py +3 -3
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/rse/protocols/bittorrent.py +14 -1
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/rse/protocols/gfal.py +1 -1
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/rse/protocols/gsiftp.py +15 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/rse/protocols/posix.py +1 -1
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/rse/protocols/protocol.py +16 -298
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/rse/protocols/rclone.py +2 -1
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/rse/protocols/rfio.py +9 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/rse/protocols/ssh.py +2 -1
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/rse/protocols/xrootd.py +2 -1
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/rse/rsemanager.py +11 -8
- rucio_clients-36.0.0rc2/lib/rucio/rse/translation.py +268 -0
- rucio_clients-36.0.0rc2/lib/rucio/vcsversion.py +11 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/version.py +7 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio_clients.egg-info/SOURCES.txt +5 -7
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/pyproject.toml +20 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/requirements/requirements.client.txt +1 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/setup.py +1 -1
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/setuputil.py +2 -1
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_belleii.py +1 -1
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_bin_rucio.py +2 -1
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_conveyor.py +2 -1
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_download.py +1 -15
- rucio_clients-36.0.0rc2/tests/test_policy_package.py +60 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_rse.py +4 -4
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_rse_lfn2path.py +1 -1
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_rse_protocol_rclone.py +2 -1
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_rse_protocol_rsync.py +2 -1
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_rse_protocol_ssh.py +2 -1
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_upload.py +6 -1
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_utils.py +2 -24
- rucio_clients-35.6.0/lib/rucio/common/cache.py +0 -74
- rucio_clients-35.6.0/lib/rucio/common/extra.py +0 -36
- rucio_clients-35.6.0/lib/rucio/common/schema/atlas.py +0 -413
- rucio_clients-35.6.0/lib/rucio/common/schema/belleii.py +0 -408
- rucio_clients-35.6.0/lib/rucio/common/schema/domatpc.py +0 -401
- rucio_clients-35.6.0/lib/rucio/common/schema/escape.py +0 -426
- rucio_clients-35.6.0/lib/rucio/common/schema/icecube.py +0 -406
- rucio_clients-35.6.0/lib/rucio/vcsversion.py +0 -11
- rucio_clients-35.6.0/tests/test_common_types.py +0 -50
- rucio_clients-35.6.0/tests/test_didtype.py +0 -96
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/AUTHORS.rst +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/ChangeLog +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/LICENSE +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/MANIFEST.in +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/README.md +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/bin/rucio-admin +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/etc/rse-accounts.cfg.template +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/__init__.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/alembicrevision.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/client/__init__.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/client/accountclient.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/client/accountlimitclient.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/client/client.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/client/configclient.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/client/credentialclient.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/client/didclient.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/client/diracclient.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/client/exportclient.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/client/fileclient.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/client/importclient.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/client/lifetimeclient.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/client/lockclient.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/client/metaconventionsclient.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/client/pingclient.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/client/replicaclient.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/client/requestclient.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/client/ruleclient.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/client/scopeclient.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/client/subscriptionclient.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/client/touchclient.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/common/__init__.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/common/constraints.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/common/pcache.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/common/policy.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/common/schema/generic.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/common/schema/generic_multi_vo.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/common/stomp_utils.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/common/stopwatch.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/common/test_rucio_server.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/rse/protocols/__init__.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/rse/protocols/cache.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/rse/protocols/dummy.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/rse/protocols/globus.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/rse/protocols/http_cache.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/rse/protocols/mock.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/rse/protocols/ngarc.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/rse/protocols/srm.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/rse/protocols/storm.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/lib/rucio/rse/protocols/webdav.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/pylintrc +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/setup.cfg +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_abacus_account.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_abacus_collection_replica.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_abacus_rse.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_account.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_account_limits.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_archive.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_auditor.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_auditor_hdfs.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_auditor_srmdumps.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_authentication.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_automatix.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_bad_replica.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_bb8.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_boolean.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_clients.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_config.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_conveyor_submitter.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_counter.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_credential.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_curl.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_daemons.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_dataset_replicas.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_db.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_did.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_did_meta_plugins.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_dumper.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_dumper_consistency.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_dumper_data_model.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_dumper_path_parsing.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_filter_engine.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_gateway_external_representation.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_heartbeat.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_hermes.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_identity.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_impl_upload_download.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_import_export.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_judge_cleaner.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_judge_evaluator.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_judge_injector.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_judge_repairer.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_lifetime.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_message.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_meta_conventions.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_meta_did.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_module_import.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_monitor.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_multi_vo.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_naming_convention.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_oauthmanager.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_oidc.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_permission.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_pfns.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_ping.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_preparer.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_qos.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_quarantined_replica.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_reaper.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_redirect.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_replica.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_replica_recoverer.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_replica_sorting.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_request.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_root_proxy.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_rse_expression_parser.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_rse_protocol_gfal2.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_rse_protocol_gfal2_impl.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_rse_protocol_posix.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_rse_protocol_srm.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_rse_protocol_webdav.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_rse_protocol_xrootd.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_rse_selector.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_rucio_server.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_rule.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_scope.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_subscription.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_throttler.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_tpc.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_trace.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_transfer.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_transfer_plugins.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tests/test_undertaker.py +0 -0
- {rucio_clients-35.6.0 → rucio_clients-36.0.0rc2}/tools/merge_rucio_configs.py +0 -0
|
@@ -37,6 +37,7 @@ from tabulate import tabulate
|
|
|
37
37
|
# rucio module has the same name as this executable module, so this rule fails. pylint: disable=no-name-in-module
|
|
38
38
|
from rucio import version
|
|
39
39
|
from rucio.client import Client
|
|
40
|
+
from rucio.common.client import detect_client_location
|
|
40
41
|
from rucio.common.config import config_get, config_get_float
|
|
41
42
|
from rucio.common.constants import ReplicaState
|
|
42
43
|
from rucio.common.exception import (
|
|
@@ -60,7 +61,7 @@ from rucio.common.exception import (
|
|
|
60
61
|
)
|
|
61
62
|
from rucio.common.extra import import_extras
|
|
62
63
|
from rucio.common.test_rucio_server import TestRucioServer
|
|
63
|
-
from rucio.common.utils import Color, StoreAndDeprecateWarningAction, chunks,
|
|
64
|
+
from rucio.common.utils import Color, StoreAndDeprecateWarningAction, chunks, extract_scope, parse_did_filter_from_string, parse_did_filter_from_string_fe, setup_logger, sizefmt
|
|
64
65
|
|
|
65
66
|
EXTRA_MODULES = import_extras(['argcomplete'])
|
|
66
67
|
|
|
@@ -29,8 +29,9 @@ request_retries = 3
|
|
|
29
29
|
auth_type = x509_proxy
|
|
30
30
|
|
|
31
31
|
[policy]
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
package = atlas_rucio_policy_package
|
|
33
|
+
#permission = atlas
|
|
34
|
+
#schema = atlas
|
|
34
35
|
lfn2pfn_algorithm_default = hash
|
|
35
36
|
support = hn-atlas-dist-analysis-help@cern.ch
|
|
36
37
|
support_rucio = https://github.com/rucio/rucio/issues/
|
|
@@ -229,9 +229,10 @@ panda_url = http://bigpanda.cern.ch/jobs/?category=analysis&jobstatus=running
|
|
|
229
229
|
window = 604800
|
|
230
230
|
|
|
231
231
|
[policy]
|
|
232
|
-
package =
|
|
232
|
+
package = atlas_rucio_policy_package
|
|
233
233
|
permission = atlas
|
|
234
234
|
schema = atlas
|
|
235
|
+
extract_scope = atlas
|
|
235
236
|
lfn2pfn_algorithm_default = hash
|
|
236
237
|
support = hn-atlas-dist-analysis-help@cern.ch
|
|
237
238
|
support_rucio = https://github.com/rucio/rucio/issues/
|
|
@@ -22,13 +22,11 @@ import os
|
|
|
22
22
|
import secrets
|
|
23
23
|
import sys
|
|
24
24
|
import time
|
|
25
|
-
from collections.abc import Generator
|
|
26
25
|
from configparser import NoOptionError, NoSectionError
|
|
27
|
-
from logging import Logger
|
|
28
26
|
from os import environ, fdopen, geteuid, makedirs, path
|
|
29
27
|
from shutil import move
|
|
30
28
|
from tempfile import mkstemp
|
|
31
|
-
from typing import Any, Optional
|
|
29
|
+
from typing import TYPE_CHECKING, Any, Optional
|
|
32
30
|
from urllib.parse import urlparse
|
|
33
31
|
|
|
34
32
|
import requests
|
|
@@ -44,6 +42,10 @@ from rucio.common.exception import CannotAuthenticate, ClientProtocolNotSupporte
|
|
|
44
42
|
from rucio.common.extra import import_extras
|
|
45
43
|
from rucio.common.utils import build_url, get_tmp_dir, my_key_generator, parse_response, setup_logger, ssh_sign
|
|
46
44
|
|
|
45
|
+
if TYPE_CHECKING:
|
|
46
|
+
from collections.abc import Generator
|
|
47
|
+
from logging import Logger
|
|
48
|
+
|
|
47
49
|
EXTRA_MODULES = import_extras(['requests_kerberos'])
|
|
48
50
|
|
|
49
51
|
if EXTRA_MODULES['requests_kerberos']:
|
|
@@ -90,7 +92,7 @@ class BaseClient:
|
|
|
90
92
|
timeout: Optional[int] = 600,
|
|
91
93
|
user_agent: Optional[str] = 'rucio-clients',
|
|
92
94
|
vo: Optional[str] = None,
|
|
93
|
-
logger: Logger = LOG) -> None:
|
|
95
|
+
logger: 'Logger' = LOG) -> None:
|
|
94
96
|
"""
|
|
95
97
|
Constructor of the BaseClient.
|
|
96
98
|
:param rucio_host: The address of the rucio server, if None it is read from the config file.
|
|
@@ -364,7 +366,7 @@ class BaseClient:
|
|
|
364
366
|
else:
|
|
365
367
|
return exception.RucioException, "%s: %s" % (exc_cls, exc_msg)
|
|
366
368
|
|
|
367
|
-
def _load_json_data(self, response: requests.Response) -> Generator[Any, Any, Any]:
|
|
369
|
+
def _load_json_data(self, response: requests.Response) -> 'Generator[Any, Any, Any]':
|
|
368
370
|
"""
|
|
369
371
|
Helper method to correctly load json data based on the content type of the http response.
|
|
370
372
|
|
|
@@ -29,11 +29,13 @@ from typing import TYPE_CHECKING, Any, Optional
|
|
|
29
29
|
|
|
30
30
|
from rucio import version
|
|
31
31
|
from rucio.client.client import Client
|
|
32
|
+
from rucio.common.checksum import CHECKSUM_ALGO_DICT, GLOBALLY_SUPPORTED_CHECKSUMS, PREFERRED_CHECKSUM, adler32
|
|
33
|
+
from rucio.common.client import detect_client_location
|
|
32
34
|
from rucio.common.config import config_get
|
|
33
35
|
from rucio.common.didtype import DID
|
|
34
36
|
from rucio.common.exception import InputValidationError, NoFilesDownloaded, NotAllFilesDownloaded, RucioException
|
|
35
37
|
from rucio.common.pcache import Pcache
|
|
36
|
-
from rucio.common.utils import
|
|
38
|
+
from rucio.common.utils import execute, extract_scope, generate_uuid, parse_replicas_from_file, parse_replicas_from_string, send_trace, sizefmt
|
|
37
39
|
from rucio.rse import rsemanager as rsemgr
|
|
38
40
|
|
|
39
41
|
if TYPE_CHECKING:
|
|
@@ -24,7 +24,7 @@ from rucio.common.utils import build_url
|
|
|
24
24
|
if TYPE_CHECKING:
|
|
25
25
|
from collections.abc import Iterable, Iterator
|
|
26
26
|
|
|
27
|
-
from rucio.common.constants import
|
|
27
|
+
from rucio.common.constants import RSE_ALL_SUPPORTED_PROTOCOL_OPERATIONS_LITERAL, RSE_SUPPORTED_PROTOCOL_DOMAINS_LITERAL, SUPPORTED_PROTOCOLS_LITERAL
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
class RSEClient(BaseClient):
|
|
@@ -238,7 +238,7 @@ class RSEClient(BaseClient):
|
|
|
238
238
|
self,
|
|
239
239
|
rse: str,
|
|
240
240
|
protocol_domain: "RSE_SUPPORTED_PROTOCOL_DOMAINS_LITERAL" = 'ALL',
|
|
241
|
-
operation: Optional["
|
|
241
|
+
operation: Optional["RSE_ALL_SUPPORTED_PROTOCOL_OPERATIONS_LITERAL"] = None,
|
|
242
242
|
default: bool = False,
|
|
243
243
|
scheme: Optional['SUPPORTED_PROTOCOLS_LITERAL'] = None
|
|
244
244
|
) -> Any:
|
|
@@ -287,7 +287,7 @@ class RSEClient(BaseClient):
|
|
|
287
287
|
rse: str,
|
|
288
288
|
lfns: 'Iterable[str]',
|
|
289
289
|
protocol_domain: 'RSE_SUPPORTED_PROTOCOL_DOMAINS_LITERAL' = 'ALL',
|
|
290
|
-
operation: Optional['
|
|
290
|
+
operation: Optional['RSE_ALL_SUPPORTED_PROTOCOL_OPERATIONS_LITERAL'] = None,
|
|
291
291
|
scheme: Optional['SUPPORTED_PROTOCOLS_LITERAL'] = None
|
|
292
292
|
) -> dict[str, str]:
|
|
293
293
|
"""
|
|
@@ -409,7 +409,7 @@ class RSEClient(BaseClient):
|
|
|
409
409
|
self,
|
|
410
410
|
rse: str,
|
|
411
411
|
domain: 'RSE_SUPPORTED_PROTOCOL_DOMAINS_LITERAL',
|
|
412
|
-
operation: '
|
|
412
|
+
operation: 'RSE_ALL_SUPPORTED_PROTOCOL_OPERATIONS_LITERAL',
|
|
413
413
|
scheme_a: 'SUPPORTED_PROTOCOLS_LITERAL',
|
|
414
414
|
scheme_b: 'SUPPORTED_PROTOCOLS_LITERAL'
|
|
415
415
|
) -> bool:
|
|
@@ -25,6 +25,9 @@ from typing import TYPE_CHECKING, Any, Final, Optional, Union, cast
|
|
|
25
25
|
|
|
26
26
|
from rucio import version
|
|
27
27
|
from rucio.client.client import Client
|
|
28
|
+
from rucio.common.bittorrent import bittorrent_v2_merkle_sha256
|
|
29
|
+
from rucio.common.checksum import GLOBALLY_SUPPORTED_CHECKSUMS, adler32, md5
|
|
30
|
+
from rucio.common.client import detect_client_location
|
|
28
31
|
from rucio.common.config import config_get, config_get_bool, config_get_int
|
|
29
32
|
from rucio.common.constants import RseAttr
|
|
30
33
|
from rucio.common.exception import (
|
|
@@ -42,7 +45,7 @@ from rucio.common.exception import (
|
|
|
42
45
|
ScopeNotFound,
|
|
43
46
|
ServiceUnavailable,
|
|
44
47
|
)
|
|
45
|
-
from rucio.common.utils import
|
|
48
|
+
from rucio.common.utils import execute, generate_uuid, make_valid_did, retry, send_trace
|
|
46
49
|
from rucio.rse import rsemanager as rsemgr
|
|
47
50
|
|
|
48
51
|
if TYPE_CHECKING:
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# Copyright European Organization for Nuclear Research (CERN) since 2012
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
import copy
|
|
15
|
+
import hashlib
|
|
16
|
+
import itertools
|
|
17
|
+
import math
|
|
18
|
+
import os
|
|
19
|
+
import time
|
|
20
|
+
from typing import TYPE_CHECKING, Any, Optional, Union
|
|
21
|
+
|
|
22
|
+
from rucio.common.exception import RucioException
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from _typeshed import FileDescriptorOrPath
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _next_pow2(num: int) -> int:
|
|
29
|
+
if not num:
|
|
30
|
+
return 0
|
|
31
|
+
return math.ceil(math.log2(num))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _bittorrent_v2_piece_length_pow2(file_size: int) -> int:
|
|
35
|
+
"""
|
|
36
|
+
Automatically chooses the `piece size` so that `piece layers`
|
|
37
|
+
is kept small(er) than usually. This is a balancing act:
|
|
38
|
+
having a big piece_length requires more work on bittorrent client
|
|
39
|
+
side to validate hashes, but having it small requires more
|
|
40
|
+
place to store the `piece layers` in the database.
|
|
41
|
+
|
|
42
|
+
Returns the result as the exponent 'x' for power of 2.
|
|
43
|
+
To get the actual length in bytes, the caller should compute 2^x.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
# by the bittorrent v2 specification, the piece size is equal to block size = 16KiB
|
|
47
|
+
min_piece_len_pow2 = 14 # 2 ** 14 == 16 KiB
|
|
48
|
+
if not file_size:
|
|
49
|
+
return min_piece_len_pow2
|
|
50
|
+
# Limit the maximum size of pieces_layers hash chain for bittorrent v2,
|
|
51
|
+
# because we'll have to store it in the database
|
|
52
|
+
max_pieces_layers_size_pow2 = 20 # 2 ** 20 == 1 MiB
|
|
53
|
+
# sha256 requires 2 ** 5 == 32 Bytes == 256 bits
|
|
54
|
+
hash_size_pow2 = 5
|
|
55
|
+
|
|
56
|
+
# The closest power of two bigger than the file size
|
|
57
|
+
file_size_pow2 = _next_pow2(file_size)
|
|
58
|
+
|
|
59
|
+
# Compute the target size for the 'pieces layers' in the torrent
|
|
60
|
+
# (as power of two: the closest power-of-two smaller than the number)
|
|
61
|
+
# Will cap at max_pieces_layers_size for files larger than 1TB.
|
|
62
|
+
target_pieces_layers_size = math.sqrt(file_size)
|
|
63
|
+
target_pieces_layers_size_pow2 = min(math.floor(math.log2(target_pieces_layers_size)), max_pieces_layers_size_pow2)
|
|
64
|
+
target_piece_num_pow2 = max(target_pieces_layers_size_pow2 - hash_size_pow2, 0)
|
|
65
|
+
|
|
66
|
+
piece_length_pow2 = max(file_size_pow2 - target_piece_num_pow2, min_piece_len_pow2)
|
|
67
|
+
return piece_length_pow2
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def bittorrent_v2_piece_length(file_size: int) -> int:
|
|
71
|
+
return 2 ** _bittorrent_v2_piece_length_pow2(file_size)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def bittorrent_v2_merkle_sha256(file: "FileDescriptorOrPath") -> tuple[bytes, bytes, int]:
|
|
75
|
+
"""
|
|
76
|
+
Compute the .torrent v2 hash tree for the given file.
|
|
77
|
+
(http://www.bittorrent.org/beps/bep_0052.html)
|
|
78
|
+
In particular, it will return the root of the merkle hash
|
|
79
|
+
tree of the file, the 'piece layers' as described in the
|
|
80
|
+
previous BEP, and the chosen `piece size`
|
|
81
|
+
|
|
82
|
+
This function will read the file in chunks of 16KiB
|
|
83
|
+
(which is the imposed block size by bittorrent v2) and compute
|
|
84
|
+
the sha256 hash of each block. When enough blocks are read
|
|
85
|
+
to form a `piece`, will compute the merkle hash root of the
|
|
86
|
+
piece from the hashes of its blocks. At the end, the hashes
|
|
87
|
+
of pieces are combined to create the global pieces_root.
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
# by the bittorrent v2 specification, the block size and the
|
|
91
|
+
# minimum piece size are both fixed to 16KiB
|
|
92
|
+
block_size = 16384
|
|
93
|
+
block_size_pow2 = 14 # 2 ** 14 == 16 KiB
|
|
94
|
+
# sha256 requires 2 ** 5 == 32 Bytes == 256 bits
|
|
95
|
+
hash_size = 32
|
|
96
|
+
|
|
97
|
+
def _merkle_root(leafs: list[bytes], nb_levels: int, padding: bytes) -> bytes:
|
|
98
|
+
"""
|
|
99
|
+
Build the root of the merkle hash tree from the (possibly incomplete) leafs layer.
|
|
100
|
+
If len(leafs) < 2 ** nb_levels, it will be padded with the padding repeated as many times
|
|
101
|
+
as needed to have 2 ** nb_levels leafs in total.
|
|
102
|
+
"""
|
|
103
|
+
nodes = copy.copy(leafs)
|
|
104
|
+
level = nb_levels
|
|
105
|
+
|
|
106
|
+
while level > 0:
|
|
107
|
+
for i in range(2 ** (level - 1)):
|
|
108
|
+
node1 = nodes[2 * i] if 2 * i < len(nodes) else padding
|
|
109
|
+
node2 = nodes[2 * i + 1] if 2 * i + 1 < len(nodes) else padding
|
|
110
|
+
h = hashlib.sha256(node1)
|
|
111
|
+
h.update(node2)
|
|
112
|
+
if i < len(nodes):
|
|
113
|
+
nodes[i] = h.digest()
|
|
114
|
+
else:
|
|
115
|
+
nodes.append(h.digest())
|
|
116
|
+
level -= 1
|
|
117
|
+
return nodes[0] if nodes else padding
|
|
118
|
+
|
|
119
|
+
file_size = os.stat(file).st_size
|
|
120
|
+
piece_length_pow2 = _bittorrent_v2_piece_length_pow2(file_size)
|
|
121
|
+
|
|
122
|
+
block_per_piece_pow2 = piece_length_pow2 - block_size_pow2
|
|
123
|
+
piece_length = 2 ** piece_length_pow2
|
|
124
|
+
block_per_piece = 2 ** block_per_piece_pow2
|
|
125
|
+
piece_num = math.ceil(file_size / piece_length)
|
|
126
|
+
|
|
127
|
+
remaining = file_size
|
|
128
|
+
remaining_in_block = min(file_size, block_size)
|
|
129
|
+
block_hashes = []
|
|
130
|
+
piece_hashes = []
|
|
131
|
+
current_hash = hashlib.sha256()
|
|
132
|
+
block_padding = bytes(hash_size)
|
|
133
|
+
with open(file, 'rb') as f:
|
|
134
|
+
while True:
|
|
135
|
+
data = f.read(remaining_in_block)
|
|
136
|
+
if not data:
|
|
137
|
+
break
|
|
138
|
+
|
|
139
|
+
current_hash.update(data)
|
|
140
|
+
|
|
141
|
+
remaining_in_block -= len(data)
|
|
142
|
+
remaining -= len(data)
|
|
143
|
+
|
|
144
|
+
if not remaining_in_block:
|
|
145
|
+
block_hashes.append(current_hash.digest())
|
|
146
|
+
if len(block_hashes) == block_per_piece or not remaining:
|
|
147
|
+
piece_hashes.append(_merkle_root(block_hashes, nb_levels=block_per_piece_pow2, padding=block_padding))
|
|
148
|
+
block_hashes = []
|
|
149
|
+
current_hash = hashlib.sha256()
|
|
150
|
+
remaining_in_block = min(block_size, remaining)
|
|
151
|
+
|
|
152
|
+
if not remaining:
|
|
153
|
+
break
|
|
154
|
+
|
|
155
|
+
if remaining or remaining_in_block or len(piece_hashes) != piece_num:
|
|
156
|
+
raise RucioException(f'Error while computing merkle sha256 of {file}')
|
|
157
|
+
|
|
158
|
+
piece_padding = _merkle_root([], nb_levels=block_per_piece_pow2, padding=block_padding)
|
|
159
|
+
pieces_root = _merkle_root(piece_hashes, nb_levels=_next_pow2(piece_num), padding=piece_padding)
|
|
160
|
+
pieces_layers = b''.join(piece_hashes) if len(piece_hashes) > 1 else b''
|
|
161
|
+
|
|
162
|
+
return pieces_root, pieces_layers, piece_length
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def bencode(obj: Union[int, bytes, str, list, dict[bytes, Any]]) -> bytes:
|
|
166
|
+
"""
|
|
167
|
+
Copied from the reference implementation of v2 bittorrent:
|
|
168
|
+
http://bittorrent.org/beps/bep_0052_torrent_creator.py
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
if isinstance(obj, int):
|
|
172
|
+
return b"i" + str(obj).encode() + b"e"
|
|
173
|
+
elif isinstance(obj, bytes):
|
|
174
|
+
return str(len(obj)).encode() + b":" + obj
|
|
175
|
+
elif isinstance(obj, str):
|
|
176
|
+
return bencode(obj.encode("utf-8"))
|
|
177
|
+
elif isinstance(obj, list):
|
|
178
|
+
return b"l" + b"".join(map(bencode, obj)) + b"e"
|
|
179
|
+
elif isinstance(obj, dict):
|
|
180
|
+
if all(isinstance(i, bytes) for i in obj.keys()):
|
|
181
|
+
items = list(obj.items())
|
|
182
|
+
items.sort()
|
|
183
|
+
return b"d" + b"".join(map(bencode, itertools.chain(*items))) + b"e"
|
|
184
|
+
else:
|
|
185
|
+
raise ValueError("dict keys should be bytes " + str(obj.keys()))
|
|
186
|
+
raise ValueError("Allowed types: int, bytes, str, list, dict; not %s", type(obj))
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def construct_torrent(
|
|
190
|
+
scope: str,
|
|
191
|
+
name: str,
|
|
192
|
+
length: int,
|
|
193
|
+
piece_length: int,
|
|
194
|
+
pieces_root: bytes,
|
|
195
|
+
pieces_layers: "Optional[bytes]" = None,
|
|
196
|
+
trackers: "Optional[list[str]]" = None,
|
|
197
|
+
) -> "tuple[str, bytes]":
|
|
198
|
+
|
|
199
|
+
torrent_dict = {
|
|
200
|
+
b'creation date': int(time.time()),
|
|
201
|
+
b'info': {
|
|
202
|
+
b'meta version': 2,
|
|
203
|
+
b'private': 1,
|
|
204
|
+
b'name': f'{scope}:{name}'.encode(),
|
|
205
|
+
b'piece length': piece_length,
|
|
206
|
+
b'file tree': {
|
|
207
|
+
name.encode(): {
|
|
208
|
+
b'': {
|
|
209
|
+
b'length': length,
|
|
210
|
+
b'pieces root': pieces_root,
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
b'piece layers': {},
|
|
216
|
+
}
|
|
217
|
+
if trackers:
|
|
218
|
+
torrent_dict[b'announce'] = trackers[0].encode()
|
|
219
|
+
if len(trackers) > 1:
|
|
220
|
+
torrent_dict[b'announce-list'] = [t.encode() for t in trackers]
|
|
221
|
+
if pieces_layers:
|
|
222
|
+
torrent_dict[b'piece layers'][pieces_root] = pieces_layers
|
|
223
|
+
|
|
224
|
+
torrent_id = hashlib.sha256(bencode(torrent_dict[b'info'])).hexdigest()[:40]
|
|
225
|
+
torrent = bencode(torrent_dict)
|
|
226
|
+
return torrent_id, torrent
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def merkle_sha256(file: "FileDescriptorOrPath") -> str:
|
|
230
|
+
"""
|
|
231
|
+
The root of the sha256 merkle hash tree with leaf size of 16 KiB.
|
|
232
|
+
"""
|
|
233
|
+
pieces_root, _, _ = bittorrent_v2_merkle_sha256(file)
|
|
234
|
+
return pieces_root.hex()
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Copyright European Organization for Nuclear Research (CERN) since 2012
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from typing import TYPE_CHECKING, Optional
|
|
16
|
+
|
|
17
|
+
from dogpile.cache.region import CacheRegion
|
|
18
|
+
|
|
19
|
+
from rucio.common.client import is_client
|
|
20
|
+
from rucio.common.config import config_get
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from collections.abc import Callable
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
CACHE_URL = config_get('cache', 'url', False, '127.0.0.1:11211', check_config_table=False)
|
|
27
|
+
|
|
28
|
+
ENABLE_CACHING = True
|
|
29
|
+
_mc_client = None
|
|
30
|
+
try:
|
|
31
|
+
if is_client():
|
|
32
|
+
ENABLE_CACHING = False
|
|
33
|
+
else:
|
|
34
|
+
import pymemcache
|
|
35
|
+
_mc_client = pymemcache.Client(CACHE_URL, connect_timeout=1, timeout=1)
|
|
36
|
+
_mc_client.version()
|
|
37
|
+
except OSError:
|
|
38
|
+
ENABLE_CACHING = False
|
|
39
|
+
except ImportError:
|
|
40
|
+
ENABLE_CACHING = False
|
|
41
|
+
finally:
|
|
42
|
+
if _mc_client:
|
|
43
|
+
_mc_client.close()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class MemcacheRegion(CacheRegion):
|
|
47
|
+
"""
|
|
48
|
+
Subclass of CacheRegion.
|
|
49
|
+
It uses pymemcache as backend if ENABLE_CACHING is True,
|
|
50
|
+
otherwise it it configured to null.
|
|
51
|
+
"""
|
|
52
|
+
def __init__(
|
|
53
|
+
self,
|
|
54
|
+
expiration_time: int,
|
|
55
|
+
function_key_generator: Optional['Callable'] = None,
|
|
56
|
+
memcached_expire_time: Optional[int] = None
|
|
57
|
+
):
|
|
58
|
+
if function_key_generator:
|
|
59
|
+
super().__init__(function_key_generator=function_key_generator)
|
|
60
|
+
else:
|
|
61
|
+
super().__init__()
|
|
62
|
+
self._configure_region(expiration_time, memcached_expire_time)
|
|
63
|
+
|
|
64
|
+
def _configure_region(
|
|
65
|
+
self,
|
|
66
|
+
expiration_time: int,
|
|
67
|
+
memcached_expire_time: Optional[int]
|
|
68
|
+
) -> None:
|
|
69
|
+
if ENABLE_CACHING:
|
|
70
|
+
self.configure(
|
|
71
|
+
'dogpile.cache.pymemcache',
|
|
72
|
+
expiration_time=expiration_time,
|
|
73
|
+
arguments={
|
|
74
|
+
'url': CACHE_URL,
|
|
75
|
+
'distributed_lock': True,
|
|
76
|
+
'memcached_expire_time': memcached_expire_time if memcached_expire_time else expiration_time + 60, # must be bigger than expiration_time
|
|
77
|
+
}
|
|
78
|
+
)
|
|
79
|
+
else:
|
|
80
|
+
self.configure('dogpile.cache.null')
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class CacheKey:
|
|
84
|
+
"""
|
|
85
|
+
Helper class to generate cache keys
|
|
86
|
+
based on sections and options.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
@staticmethod
|
|
90
|
+
def _generate_key(*args: str) -> str:
|
|
91
|
+
return '_'.join(args)
|
|
92
|
+
|
|
93
|
+
@staticmethod
|
|
94
|
+
def has_section(section: str) -> str:
|
|
95
|
+
return CacheKey._generate_key('has_section', section)
|
|
96
|
+
|
|
97
|
+
@staticmethod
|
|
98
|
+
def options(section: str) -> str:
|
|
99
|
+
return CacheKey._generate_key('options', section)
|
|
100
|
+
|
|
101
|
+
@staticmethod
|
|
102
|
+
def has_option(section: str, option: str) -> str:
|
|
103
|
+
return CacheKey._generate_key('has_option', section, option)
|
|
104
|
+
|
|
105
|
+
@staticmethod
|
|
106
|
+
def items(section: str) -> str:
|
|
107
|
+
return CacheKey._generate_key('items', section)
|
|
108
|
+
|
|
109
|
+
@staticmethod
|
|
110
|
+
def value(section: str, option: str) -> str:
|
|
111
|
+
return CacheKey._generate_key('get', section, option)
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Copyright European Organization for Nuclear Research (CERN) since 2012
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
import hashlib
|
|
15
|
+
import io
|
|
16
|
+
import mmap
|
|
17
|
+
import zlib
|
|
18
|
+
from functools import partial
|
|
19
|
+
from typing import TYPE_CHECKING
|
|
20
|
+
|
|
21
|
+
from rucio.common.bittorrent import merkle_sha256
|
|
22
|
+
from rucio.common.exception import ChecksumCalculationError
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from _typeshed import FileDescriptorOrPath
|
|
26
|
+
|
|
27
|
+
# GLOBALLY_SUPPORTED_CHECKSUMS = ['adler32', 'md5', 'sha256', 'crc32']
|
|
28
|
+
GLOBALLY_SUPPORTED_CHECKSUMS = ['adler32', 'md5']
|
|
29
|
+
PREFERRED_CHECKSUM = GLOBALLY_SUPPORTED_CHECKSUMS[0]
|
|
30
|
+
CHECKSUM_KEY = 'supported_checksums'
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def is_checksum_valid(checksum_name: str) -> bool:
|
|
34
|
+
"""
|
|
35
|
+
A simple function to check whether a checksum algorithm is supported.
|
|
36
|
+
Relies on GLOBALLY_SUPPORTED_CHECKSUMS to allow for expandability.
|
|
37
|
+
|
|
38
|
+
:param checksum_name: The name of the checksum to be verified.
|
|
39
|
+
:returns: True if checksum_name is in GLOBALLY_SUPPORTED_CHECKSUMS list, False otherwise.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
return checksum_name in GLOBALLY_SUPPORTED_CHECKSUMS
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def set_preferred_checksum(checksum_name: str) -> None:
|
|
46
|
+
"""
|
|
47
|
+
If the input checksum name is valid,
|
|
48
|
+
set it as PREFERRED_CHECKSUM.
|
|
49
|
+
|
|
50
|
+
:param checksum_name: The name of the checksum to be verified.
|
|
51
|
+
"""
|
|
52
|
+
if is_checksum_valid(checksum_name):
|
|
53
|
+
global PREFERRED_CHECKSUM
|
|
54
|
+
PREFERRED_CHECKSUM = checksum_name
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def adler32(file: "FileDescriptorOrPath") -> str:
|
|
58
|
+
"""
|
|
59
|
+
An Adler-32 checksum is obtained by calculating two 16-bit checksums A and B
|
|
60
|
+
and concatenating their bits into a 32-bit integer. A is the sum of all bytes in the
|
|
61
|
+
stream plus one, and B is the sum of the individual values of A from each step.
|
|
62
|
+
|
|
63
|
+
:param file: file name
|
|
64
|
+
:returns: Hexified string, padded to 8 values.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
# adler starting value is _not_ 0
|
|
68
|
+
adler = 1
|
|
69
|
+
|
|
70
|
+
can_mmap = False
|
|
71
|
+
# try:
|
|
72
|
+
# with open(file, 'r+b') as f:
|
|
73
|
+
# can_mmap = True
|
|
74
|
+
# except:
|
|
75
|
+
# pass
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
# use mmap if possible
|
|
79
|
+
if can_mmap:
|
|
80
|
+
with open(file, 'r+b') as f:
|
|
81
|
+
m = mmap.mmap(f.fileno(), 0)
|
|
82
|
+
# partial block reads at slightly increased buffer sizes
|
|
83
|
+
for block in iter(partial(m.read, io.DEFAULT_BUFFER_SIZE * 8), b''):
|
|
84
|
+
adler = zlib.adler32(block, adler)
|
|
85
|
+
else:
|
|
86
|
+
with open(file, 'rb') as f:
|
|
87
|
+
# partial block reads at slightly increased buffer sizes
|
|
88
|
+
for block in iter(partial(f.read, io.DEFAULT_BUFFER_SIZE * 8), b''):
|
|
89
|
+
adler = zlib.adler32(block, adler)
|
|
90
|
+
|
|
91
|
+
except Exception as e:
|
|
92
|
+
raise ChecksumCalculationError('adler32', str(file), e)
|
|
93
|
+
|
|
94
|
+
# backflip on 32bit -- can be removed once everything is fully migrated to 64bit
|
|
95
|
+
if adler < 0:
|
|
96
|
+
adler = adler + 2 ** 32
|
|
97
|
+
|
|
98
|
+
return str('%08x' % adler)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def md5(file: "FileDescriptorOrPath") -> str:
|
|
102
|
+
"""
|
|
103
|
+
Runs the MD5 algorithm (RFC-1321) on the binary content of the file named file and returns the hexadecimal digest
|
|
104
|
+
|
|
105
|
+
:param file: file name
|
|
106
|
+
:returns: string of 32 hexadecimal digits
|
|
107
|
+
"""
|
|
108
|
+
hash_md5 = hashlib.md5()
|
|
109
|
+
try:
|
|
110
|
+
with open(file, "rb") as f:
|
|
111
|
+
list(map(hash_md5.update, iter(lambda: f.read(4096), b"")))
|
|
112
|
+
except Exception as e:
|
|
113
|
+
raise ChecksumCalculationError('md5', str(file), e)
|
|
114
|
+
|
|
115
|
+
return hash_md5.hexdigest()
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def sha256(file: "FileDescriptorOrPath") -> str:
|
|
119
|
+
"""
|
|
120
|
+
Runs the SHA256 algorithm on the binary content of the file named file and returns the hexadecimal digest
|
|
121
|
+
|
|
122
|
+
:param file: file name
|
|
123
|
+
:returns: string of 32 hexadecimal digits
|
|
124
|
+
"""
|
|
125
|
+
with open(file, "rb") as f:
|
|
126
|
+
bytes_ = f.read() # read entire file as bytes
|
|
127
|
+
readable_hash = hashlib.sha256(bytes_).hexdigest()
|
|
128
|
+
print(readable_hash)
|
|
129
|
+
return readable_hash
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def crc32(file: "FileDescriptorOrPath") -> str:
|
|
133
|
+
"""
|
|
134
|
+
Runs the CRC32 algorithm on the binary content of the file named file and returns the hexadecimal digest
|
|
135
|
+
|
|
136
|
+
:param file: file name
|
|
137
|
+
:returns: string of 32 hexadecimal digits
|
|
138
|
+
"""
|
|
139
|
+
prev = 0
|
|
140
|
+
for eachLine in open(file, "rb"):
|
|
141
|
+
prev = zlib.crc32(eachLine, prev)
|
|
142
|
+
return "%X" % (prev & 0xFFFFFFFF)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
CHECKSUM_ALGO_DICT = {
|
|
146
|
+
'adler32': adler32,
|
|
147
|
+
'md5': md5,
|
|
148
|
+
'sha256': sha256,
|
|
149
|
+
'crc32': crc32,
|
|
150
|
+
'merkle_sha256': merkle_sha256
|
|
151
|
+
}
|