rucio 37.0.0rc1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of rucio might be problematic. Click here for more details.
- rucio/__init__.py +17 -0
- rucio/alembicrevision.py +15 -0
- rucio/cli/__init__.py +14 -0
- rucio/cli/account.py +216 -0
- rucio/cli/bin_legacy/__init__.py +13 -0
- rucio/cli/bin_legacy/rucio.py +2825 -0
- rucio/cli/bin_legacy/rucio_admin.py +2500 -0
- rucio/cli/command.py +272 -0
- rucio/cli/config.py +72 -0
- rucio/cli/did.py +191 -0
- rucio/cli/download.py +128 -0
- rucio/cli/lifetime_exception.py +33 -0
- rucio/cli/replica.py +162 -0
- rucio/cli/rse.py +293 -0
- rucio/cli/rule.py +158 -0
- rucio/cli/scope.py +40 -0
- rucio/cli/subscription.py +73 -0
- rucio/cli/upload.py +60 -0
- rucio/cli/utils.py +226 -0
- rucio/client/__init__.py +15 -0
- rucio/client/accountclient.py +432 -0
- rucio/client/accountlimitclient.py +183 -0
- rucio/client/baseclient.py +983 -0
- rucio/client/client.py +120 -0
- rucio/client/configclient.py +126 -0
- rucio/client/credentialclient.py +59 -0
- rucio/client/didclient.py +868 -0
- rucio/client/diracclient.py +56 -0
- rucio/client/downloadclient.py +1783 -0
- rucio/client/exportclient.py +44 -0
- rucio/client/fileclient.py +50 -0
- rucio/client/importclient.py +42 -0
- rucio/client/lifetimeclient.py +90 -0
- rucio/client/lockclient.py +109 -0
- rucio/client/metaconventionsclient.py +140 -0
- rucio/client/pingclient.py +44 -0
- rucio/client/replicaclient.py +452 -0
- rucio/client/requestclient.py +125 -0
- rucio/client/richclient.py +317 -0
- rucio/client/rseclient.py +746 -0
- rucio/client/ruleclient.py +294 -0
- rucio/client/scopeclient.py +90 -0
- rucio/client/subscriptionclient.py +173 -0
- rucio/client/touchclient.py +82 -0
- rucio/client/uploadclient.py +969 -0
- rucio/common/__init__.py +13 -0
- rucio/common/bittorrent.py +234 -0
- rucio/common/cache.py +111 -0
- rucio/common/checksum.py +168 -0
- rucio/common/client.py +122 -0
- rucio/common/config.py +788 -0
- rucio/common/constants.py +217 -0
- rucio/common/constraints.py +17 -0
- rucio/common/didtype.py +237 -0
- rucio/common/dumper/__init__.py +342 -0
- rucio/common/dumper/consistency.py +497 -0
- rucio/common/dumper/data_models.py +362 -0
- rucio/common/dumper/path_parsing.py +75 -0
- rucio/common/exception.py +1208 -0
- rucio/common/extra.py +31 -0
- rucio/common/logging.py +420 -0
- rucio/common/pcache.py +1409 -0
- rucio/common/plugins.py +185 -0
- rucio/common/policy.py +93 -0
- rucio/common/schema/__init__.py +200 -0
- rucio/common/schema/generic.py +416 -0
- rucio/common/schema/generic_multi_vo.py +395 -0
- rucio/common/stomp_utils.py +423 -0
- rucio/common/stopwatch.py +55 -0
- rucio/common/test_rucio_server.py +154 -0
- rucio/common/types.py +483 -0
- rucio/common/utils.py +1688 -0
- rucio/core/__init__.py +13 -0
- rucio/core/account.py +496 -0
- rucio/core/account_counter.py +236 -0
- rucio/core/account_limit.py +425 -0
- rucio/core/authentication.py +620 -0
- rucio/core/config.py +437 -0
- rucio/core/credential.py +224 -0
- rucio/core/did.py +3004 -0
- rucio/core/did_meta_plugins/__init__.py +252 -0
- rucio/core/did_meta_plugins/did_column_meta.py +331 -0
- rucio/core/did_meta_plugins/did_meta_plugin_interface.py +165 -0
- rucio/core/did_meta_plugins/elasticsearch_meta.py +407 -0
- rucio/core/did_meta_plugins/filter_engine.py +672 -0
- rucio/core/did_meta_plugins/json_meta.py +240 -0
- rucio/core/did_meta_plugins/mongo_meta.py +229 -0
- rucio/core/did_meta_plugins/postgres_meta.py +352 -0
- rucio/core/dirac.py +237 -0
- rucio/core/distance.py +187 -0
- rucio/core/exporter.py +59 -0
- rucio/core/heartbeat.py +363 -0
- rucio/core/identity.py +301 -0
- rucio/core/importer.py +260 -0
- rucio/core/lifetime_exception.py +377 -0
- rucio/core/lock.py +577 -0
- rucio/core/message.py +288 -0
- rucio/core/meta_conventions.py +203 -0
- rucio/core/monitor.py +448 -0
- rucio/core/naming_convention.py +195 -0
- rucio/core/nongrid_trace.py +136 -0
- rucio/core/oidc.py +1463 -0
- rucio/core/permission/__init__.py +161 -0
- rucio/core/permission/generic.py +1124 -0
- rucio/core/permission/generic_multi_vo.py +1144 -0
- rucio/core/quarantined_replica.py +224 -0
- rucio/core/replica.py +4483 -0
- rucio/core/replica_sorter.py +362 -0
- rucio/core/request.py +3091 -0
- rucio/core/rse.py +2079 -0
- rucio/core/rse_counter.py +185 -0
- rucio/core/rse_expression_parser.py +459 -0
- rucio/core/rse_selector.py +304 -0
- rucio/core/rule.py +4484 -0
- rucio/core/rule_grouping.py +1620 -0
- rucio/core/scope.py +181 -0
- rucio/core/subscription.py +362 -0
- rucio/core/topology.py +490 -0
- rucio/core/trace.py +375 -0
- rucio/core/transfer.py +1531 -0
- rucio/core/vo.py +169 -0
- rucio/core/volatile_replica.py +151 -0
- rucio/daemons/__init__.py +13 -0
- rucio/daemons/abacus/__init__.py +13 -0
- rucio/daemons/abacus/account.py +116 -0
- rucio/daemons/abacus/collection_replica.py +124 -0
- rucio/daemons/abacus/rse.py +117 -0
- rucio/daemons/atropos/__init__.py +13 -0
- rucio/daemons/atropos/atropos.py +242 -0
- rucio/daemons/auditor/__init__.py +289 -0
- rucio/daemons/auditor/hdfs.py +97 -0
- rucio/daemons/auditor/srmdumps.py +355 -0
- rucio/daemons/automatix/__init__.py +13 -0
- rucio/daemons/automatix/automatix.py +304 -0
- rucio/daemons/badreplicas/__init__.py +13 -0
- rucio/daemons/badreplicas/minos.py +322 -0
- rucio/daemons/badreplicas/minos_temporary_expiration.py +171 -0
- rucio/daemons/badreplicas/necromancer.py +196 -0
- rucio/daemons/bb8/__init__.py +13 -0
- rucio/daemons/bb8/bb8.py +353 -0
- rucio/daemons/bb8/common.py +759 -0
- rucio/daemons/bb8/nuclei_background_rebalance.py +153 -0
- rucio/daemons/bb8/t2_background_rebalance.py +153 -0
- rucio/daemons/cache/__init__.py +13 -0
- rucio/daemons/cache/consumer.py +133 -0
- rucio/daemons/common.py +405 -0
- rucio/daemons/conveyor/__init__.py +13 -0
- rucio/daemons/conveyor/common.py +562 -0
- rucio/daemons/conveyor/finisher.py +529 -0
- rucio/daemons/conveyor/poller.py +394 -0
- rucio/daemons/conveyor/preparer.py +205 -0
- rucio/daemons/conveyor/receiver.py +179 -0
- rucio/daemons/conveyor/stager.py +133 -0
- rucio/daemons/conveyor/submitter.py +403 -0
- rucio/daemons/conveyor/throttler.py +532 -0
- rucio/daemons/follower/__init__.py +13 -0
- rucio/daemons/follower/follower.py +101 -0
- rucio/daemons/hermes/__init__.py +13 -0
- rucio/daemons/hermes/hermes.py +534 -0
- rucio/daemons/judge/__init__.py +13 -0
- rucio/daemons/judge/cleaner.py +159 -0
- rucio/daemons/judge/evaluator.py +185 -0
- rucio/daemons/judge/injector.py +162 -0
- rucio/daemons/judge/repairer.py +154 -0
- rucio/daemons/oauthmanager/__init__.py +13 -0
- rucio/daemons/oauthmanager/oauthmanager.py +198 -0
- rucio/daemons/reaper/__init__.py +13 -0
- rucio/daemons/reaper/dark_reaper.py +282 -0
- rucio/daemons/reaper/reaper.py +739 -0
- rucio/daemons/replicarecoverer/__init__.py +13 -0
- rucio/daemons/replicarecoverer/suspicious_replica_recoverer.py +626 -0
- rucio/daemons/rsedecommissioner/__init__.py +13 -0
- rucio/daemons/rsedecommissioner/config.py +81 -0
- rucio/daemons/rsedecommissioner/profiles/__init__.py +24 -0
- rucio/daemons/rsedecommissioner/profiles/atlas.py +60 -0
- rucio/daemons/rsedecommissioner/profiles/generic.py +452 -0
- rucio/daemons/rsedecommissioner/profiles/types.py +93 -0
- rucio/daemons/rsedecommissioner/rse_decommissioner.py +280 -0
- rucio/daemons/storage/__init__.py +13 -0
- rucio/daemons/storage/consistency/__init__.py +13 -0
- rucio/daemons/storage/consistency/actions.py +848 -0
- rucio/daemons/tracer/__init__.py +13 -0
- rucio/daemons/tracer/kronos.py +511 -0
- rucio/daemons/transmogrifier/__init__.py +13 -0
- rucio/daemons/transmogrifier/transmogrifier.py +762 -0
- rucio/daemons/undertaker/__init__.py +13 -0
- rucio/daemons/undertaker/undertaker.py +137 -0
- rucio/db/__init__.py +13 -0
- rucio/db/sqla/__init__.py +52 -0
- rucio/db/sqla/constants.py +206 -0
- rucio/db/sqla/migrate_repo/__init__.py +13 -0
- rucio/db/sqla/migrate_repo/env.py +110 -0
- rucio/db/sqla/migrate_repo/versions/01eaf73ab656_add_new_rule_notification_state_progress.py +70 -0
- rucio/db/sqla/migrate_repo/versions/0437a40dbfd1_add_eol_at_in_rules.py +47 -0
- rucio/db/sqla/migrate_repo/versions/0f1adb7a599a_create_transfer_hops_table.py +59 -0
- rucio/db/sqla/migrate_repo/versions/102efcf145f4_added_stuck_at_column_to_rules.py +43 -0
- rucio/db/sqla/migrate_repo/versions/13d4f70c66a9_introduce_transfer_limits.py +91 -0
- rucio/db/sqla/migrate_repo/versions/140fef722e91_cleanup_distances_table.py +76 -0
- rucio/db/sqla/migrate_repo/versions/14ec5aeb64cf_add_request_external_host.py +43 -0
- rucio/db/sqla/migrate_repo/versions/156fb5b5a14_add_request_type_to_requests_idx.py +50 -0
- rucio/db/sqla/migrate_repo/versions/1677d4d803c8_split_rse_availability_into_multiple.py +68 -0
- rucio/db/sqla/migrate_repo/versions/16a0aca82e12_create_index_on_table_replicas_path.py +40 -0
- rucio/db/sqla/migrate_repo/versions/1803333ac20f_adding_provenance_and_phys_group.py +45 -0
- rucio/db/sqla/migrate_repo/versions/1a29d6a9504c_add_didtype_chck_to_requests.py +60 -0
- rucio/db/sqla/migrate_repo/versions/1a80adff031a_create_index_on_rules_hist_recent.py +40 -0
- rucio/db/sqla/migrate_repo/versions/1c45d9730ca6_increase_identity_length.py +140 -0
- rucio/db/sqla/migrate_repo/versions/1d1215494e95_add_quarantined_replicas_table.py +73 -0
- rucio/db/sqla/migrate_repo/versions/1d96f484df21_asynchronous_rules_and_rule_approval.py +74 -0
- rucio/db/sqla/migrate_repo/versions/1f46c5f240ac_add_bytes_column_to_bad_replicas.py +43 -0
- rucio/db/sqla/migrate_repo/versions/1fc15ab60d43_add_message_history_table.py +50 -0
- rucio/db/sqla/migrate_repo/versions/2190e703eb6e_move_rse_settings_to_rse_attributes.py +134 -0
- rucio/db/sqla/migrate_repo/versions/21d6b9dc9961_add_mismatch_scheme_state_to_requests.py +64 -0
- rucio/db/sqla/migrate_repo/versions/22cf51430c78_add_availability_column_to_table_rses.py +39 -0
- rucio/db/sqla/migrate_repo/versions/22d887e4ec0a_create_sources_table.py +64 -0
- rucio/db/sqla/migrate_repo/versions/25821a8a45a3_remove_unique_constraint_on_requests.py +51 -0
- rucio/db/sqla/migrate_repo/versions/25fc855625cf_added_unique_constraint_to_rules.py +41 -0
- rucio/db/sqla/migrate_repo/versions/269fee20dee9_add_repair_cnt_to_locks.py +43 -0
- rucio/db/sqla/migrate_repo/versions/271a46ea6244_add_ignore_availability_column_to_rules.py +44 -0
- rucio/db/sqla/migrate_repo/versions/277b5fbb41d3_switch_heartbeats_executable.py +53 -0
- rucio/db/sqla/migrate_repo/versions/27e3a68927fb_remove_replicas_tombstone_and_replicas_.py +38 -0
- rucio/db/sqla/migrate_repo/versions/2854cd9e168_added_rule_id_column.py +47 -0
- rucio/db/sqla/migrate_repo/versions/295289b5a800_processed_by_and__at_in_requests.py +45 -0
- rucio/db/sqla/migrate_repo/versions/2962ece31cf4_add_nbaccesses_column_in_the_did_table.py +45 -0
- rucio/db/sqla/migrate_repo/versions/2af3291ec4c_added_replicas_history_table.py +57 -0
- rucio/db/sqla/migrate_repo/versions/2b69addda658_add_columns_for_third_party_copy_read_.py +45 -0
- rucio/db/sqla/migrate_repo/versions/2b8e7bcb4783_add_config_table.py +69 -0
- rucio/db/sqla/migrate_repo/versions/2ba5229cb54c_add_submitted_at_to_requests_table.py +43 -0
- rucio/db/sqla/migrate_repo/versions/2cbee484dcf9_added_column_volume_to_rse_transfer_.py +42 -0
- rucio/db/sqla/migrate_repo/versions/2edee4a83846_add_source_to_requests_and_requests_.py +47 -0
- rucio/db/sqla/migrate_repo/versions/2eef46be23d4_change_tokens_pk.py +46 -0
- rucio/db/sqla/migrate_repo/versions/2f648fc909f3_index_in_rule_history_on_scope_name.py +40 -0
- rucio/db/sqla/migrate_repo/versions/3082b8cef557_add_naming_convention_table_and_closed_.py +67 -0
- rucio/db/sqla/migrate_repo/versions/30d5206e9cad_increase_oauthrequest_redirect_msg_.py +37 -0
- rucio/db/sqla/migrate_repo/versions/30fa38b6434e_add_index_on_service_column_in_the_message_table.py +44 -0
- rucio/db/sqla/migrate_repo/versions/3152492b110b_added_staging_area_column.py +77 -0
- rucio/db/sqla/migrate_repo/versions/32c7d2783f7e_create_bad_replicas_table.py +60 -0
- rucio/db/sqla/migrate_repo/versions/3345511706b8_replicas_table_pk_definition_is_in_.py +72 -0
- rucio/db/sqla/migrate_repo/versions/35ef10d1e11b_change_index_on_table_requests.py +42 -0
- rucio/db/sqla/migrate_repo/versions/379a19b5332d_create_rse_limits_table.py +65 -0
- rucio/db/sqla/migrate_repo/versions/384b96aa0f60_created_rule_history_tables.py +133 -0
- rucio/db/sqla/migrate_repo/versions/3ac1660a1a72_extend_distance_table.py +55 -0
- rucio/db/sqla/migrate_repo/versions/3ad36e2268b0_create_collection_replicas_updates_table.py +76 -0
- rucio/db/sqla/migrate_repo/versions/3c9df354071b_extend_waiting_request_state.py +60 -0
- rucio/db/sqla/migrate_repo/versions/3d9813fab443_add_a_new_state_lost_in_badfilesstatus.py +44 -0
- rucio/db/sqla/migrate_repo/versions/40ad39ce3160_add_transferred_at_to_requests_table.py +43 -0
- rucio/db/sqla/migrate_repo/versions/4207be2fd914_add_notification_column_to_rules.py +64 -0
- rucio/db/sqla/migrate_repo/versions/42db2617c364_create_index_on_requests_external_id.py +40 -0
- rucio/db/sqla/migrate_repo/versions/436827b13f82_added_column_activity_to_table_requests.py +43 -0
- rucio/db/sqla/migrate_repo/versions/44278720f774_update_requests_typ_sta_upd_idx_index.py +44 -0
- rucio/db/sqla/migrate_repo/versions/45378a1e76a8_create_collection_replica_table.py +78 -0
- rucio/db/sqla/migrate_repo/versions/469d262be19_removing_created_at_index.py +41 -0
- rucio/db/sqla/migrate_repo/versions/4783c1f49cb4_create_distance_table.py +59 -0
- rucio/db/sqla/migrate_repo/versions/49a21b4d4357_create_index_on_table_tokens.py +44 -0
- rucio/db/sqla/migrate_repo/versions/4a2cbedda8b9_add_source_replica_expression_column_to_.py +43 -0
- rucio/db/sqla/migrate_repo/versions/4a7182d9578b_added_bytes_length_accessed_at_columns.py +49 -0
- rucio/db/sqla/migrate_repo/versions/4bab9edd01fc_create_index_on_requests_rule_id.py +40 -0
- rucio/db/sqla/migrate_repo/versions/4c3a4acfe006_new_attr_account_table.py +63 -0
- rucio/db/sqla/migrate_repo/versions/4cf0a2e127d4_adding_transient_metadata.py +43 -0
- rucio/db/sqla/migrate_repo/versions/4df2c5ddabc0_remove_temporary_dids.py +55 -0
- rucio/db/sqla/migrate_repo/versions/50280c53117c_add_qos_class_to_rse.py +45 -0
- rucio/db/sqla/migrate_repo/versions/52153819589c_add_rse_id_to_replicas_table.py +43 -0
- rucio/db/sqla/migrate_repo/versions/52fd9f4916fa_added_activity_to_rules.py +43 -0
- rucio/db/sqla/migrate_repo/versions/53b479c3cb0f_fix_did_meta_table_missing_updated_at_.py +45 -0
- rucio/db/sqla/migrate_repo/versions/5673b4b6e843_add_wfms_metadata_to_rule_tables.py +47 -0
- rucio/db/sqla/migrate_repo/versions/575767d9f89_added_source_history_table.py +58 -0
- rucio/db/sqla/migrate_repo/versions/58bff7008037_add_started_at_to_requests.py +45 -0
- rucio/db/sqla/migrate_repo/versions/58c8b78301ab_rename_callback_to_message.py +106 -0
- rucio/db/sqla/migrate_repo/versions/5f139f77382a_added_child_rule_id_column.py +55 -0
- rucio/db/sqla/migrate_repo/versions/688ef1840840_adding_did_meta_table.py +50 -0
- rucio/db/sqla/migrate_repo/versions/6e572a9bfbf3_add_new_split_container_column_to_rules.py +47 -0
- rucio/db/sqla/migrate_repo/versions/70587619328_add_comment_column_for_subscriptions.py +43 -0
- rucio/db/sqla/migrate_repo/versions/739064d31565_remove_history_table_pks.py +41 -0
- rucio/db/sqla/migrate_repo/versions/7541902bf173_add_didsfollowed_and_followevents_table.py +91 -0
- rucio/db/sqla/migrate_repo/versions/7ec22226cdbf_new_replica_state_for_temporary_.py +72 -0
- rucio/db/sqla/migrate_repo/versions/810a41685bc1_added_columns_rse_transfer_limits.py +49 -0
- rucio/db/sqla/migrate_repo/versions/83f991c63a93_correct_rse_expression_length.py +43 -0
- rucio/db/sqla/migrate_repo/versions/8523998e2e76_increase_size_of_extended_attributes_.py +43 -0
- rucio/db/sqla/migrate_repo/versions/8ea9122275b1_adding_missing_function_based_indices.py +53 -0
- rucio/db/sqla/migrate_repo/versions/90f47792bb76_add_clob_payload_to_messages.py +45 -0
- rucio/db/sqla/migrate_repo/versions/914b8f02df38_new_table_for_lifetime_model_exceptions.py +68 -0
- rucio/db/sqla/migrate_repo/versions/94a5961ddbf2_add_estimator_columns.py +45 -0
- rucio/db/sqla/migrate_repo/versions/9a1b149a2044_add_saml_identity_type.py +94 -0
- rucio/db/sqla/migrate_repo/versions/9a45bc4ea66d_add_vp_table.py +54 -0
- rucio/db/sqla/migrate_repo/versions/9eb936a81eb1_true_is_true.py +72 -0
- rucio/db/sqla/migrate_repo/versions/a08fa8de1545_transfer_stats_table.py +55 -0
- rucio/db/sqla/migrate_repo/versions/a118956323f8_added_vo_table_and_vo_col_to_rse.py +76 -0
- rucio/db/sqla/migrate_repo/versions/a193a275255c_add_status_column_in_messages.py +47 -0
- rucio/db/sqla/migrate_repo/versions/a5f6f6e928a7_1_7_0.py +121 -0
- rucio/db/sqla/migrate_repo/versions/a616581ee47_added_columns_to_table_requests.py +59 -0
- rucio/db/sqla/migrate_repo/versions/a6eb23955c28_state_idx_non_functional.py +52 -0
- rucio/db/sqla/migrate_repo/versions/a74275a1ad30_added_global_quota_table.py +54 -0
- rucio/db/sqla/migrate_repo/versions/a93e4e47bda_heartbeats.py +64 -0
- rucio/db/sqla/migrate_repo/versions/ae2a56fcc89_added_comment_column_to_rules.py +49 -0
- rucio/db/sqla/migrate_repo/versions/b0070f3695c8_add_deletedidmeta_table.py +57 -0
- rucio/db/sqla/migrate_repo/versions/b4293a99f344_added_column_identity_to_table_tokens.py +43 -0
- rucio/db/sqla/migrate_repo/versions/b5493606bbf5_fix_primary_key_for_subscription_history.py +41 -0
- rucio/db/sqla/migrate_repo/versions/b7d287de34fd_removal_of_replicastate_source.py +91 -0
- rucio/db/sqla/migrate_repo/versions/b818052fa670_add_index_to_quarantined_replicas.py +40 -0
- rucio/db/sqla/migrate_repo/versions/b8caac94d7f0_add_comments_column_for_subscriptions_.py +43 -0
- rucio/db/sqla/migrate_repo/versions/b96a1c7e1cc4_new_bad_pfns_table_and_bad_replicas_.py +143 -0
- rucio/db/sqla/migrate_repo/versions/bb695f45c04_extend_request_state.py +76 -0
- rucio/db/sqla/migrate_repo/versions/bc68e9946deb_add_staging_timestamps_to_request.py +50 -0
- rucio/db/sqla/migrate_repo/versions/bf3baa1c1474_correct_pk_and_idx_for_history_tables.py +72 -0
- rucio/db/sqla/migrate_repo/versions/c0937668555f_add_qos_policy_map_table.py +55 -0
- rucio/db/sqla/migrate_repo/versions/c129ccdb2d5_add_lumiblocknr_to_dids.py +43 -0
- rucio/db/sqla/migrate_repo/versions/ccdbcd48206e_add_did_type_column_index_on_did_meta_.py +65 -0
- rucio/db/sqla/migrate_repo/versions/cebad904c4dd_new_payload_column_for_heartbeats.py +47 -0
- rucio/db/sqla/migrate_repo/versions/d1189a09c6e0_oauth2_0_and_jwt_feature_support_adding_.py +146 -0
- rucio/db/sqla/migrate_repo/versions/d23453595260_extend_request_state_for_preparer.py +104 -0
- rucio/db/sqla/migrate_repo/versions/d6dceb1de2d_added_purge_column_to_rules.py +44 -0
- rucio/db/sqla/migrate_repo/versions/d6e2c3b2cf26_remove_third_party_copy_column_from_rse.py +43 -0
- rucio/db/sqla/migrate_repo/versions/d91002c5841_new_account_limits_table.py +103 -0
- rucio/db/sqla/migrate_repo/versions/e138c364ebd0_extending_columns_for_filter_and_.py +49 -0
- rucio/db/sqla/migrate_repo/versions/e59300c8b179_support_for_archive.py +104 -0
- rucio/db/sqla/migrate_repo/versions/f1b14a8c2ac1_postgres_use_check_constraints.py +29 -0
- rucio/db/sqla/migrate_repo/versions/f41ffe206f37_oracle_global_temporary_tables.py +74 -0
- rucio/db/sqla/migrate_repo/versions/f85a2962b021_adding_transfertool_column_to_requests_.py +47 -0
- rucio/db/sqla/migrate_repo/versions/fa7a7d78b602_increase_refresh_token_size.py +43 -0
- rucio/db/sqla/migrate_repo/versions/fb28a95fe288_add_replicas_rse_id_tombstone_idx.py +37 -0
- rucio/db/sqla/migrate_repo/versions/fe1a65b176c9_set_third_party_copy_read_and_write_.py +43 -0
- rucio/db/sqla/migrate_repo/versions/fe8ea2fa9788_added_third_party_copy_column_to_rse_.py +43 -0
- rucio/db/sqla/models.py +1743 -0
- rucio/db/sqla/sautils.py +55 -0
- rucio/db/sqla/session.py +529 -0
- rucio/db/sqla/types.py +206 -0
- rucio/db/sqla/util.py +543 -0
- rucio/gateway/__init__.py +13 -0
- rucio/gateway/account.py +345 -0
- rucio/gateway/account_limit.py +363 -0
- rucio/gateway/authentication.py +381 -0
- rucio/gateway/config.py +227 -0
- rucio/gateway/credential.py +70 -0
- rucio/gateway/did.py +987 -0
- rucio/gateway/dirac.py +83 -0
- rucio/gateway/exporter.py +60 -0
- rucio/gateway/heartbeat.py +76 -0
- rucio/gateway/identity.py +189 -0
- rucio/gateway/importer.py +46 -0
- rucio/gateway/lifetime_exception.py +121 -0
- rucio/gateway/lock.py +153 -0
- rucio/gateway/meta_conventions.py +98 -0
- rucio/gateway/permission.py +74 -0
- rucio/gateway/quarantined_replica.py +79 -0
- rucio/gateway/replica.py +538 -0
- rucio/gateway/request.py +330 -0
- rucio/gateway/rse.py +632 -0
- rucio/gateway/rule.py +437 -0
- rucio/gateway/scope.py +100 -0
- rucio/gateway/subscription.py +280 -0
- rucio/gateway/vo.py +126 -0
- rucio/rse/__init__.py +96 -0
- rucio/rse/protocols/__init__.py +13 -0
- rucio/rse/protocols/bittorrent.py +194 -0
- rucio/rse/protocols/cache.py +111 -0
- rucio/rse/protocols/dummy.py +100 -0
- rucio/rse/protocols/gfal.py +708 -0
- rucio/rse/protocols/globus.py +243 -0
- rucio/rse/protocols/http_cache.py +82 -0
- rucio/rse/protocols/mock.py +123 -0
- rucio/rse/protocols/ngarc.py +209 -0
- rucio/rse/protocols/posix.py +250 -0
- rucio/rse/protocols/protocol.py +361 -0
- rucio/rse/protocols/rclone.py +365 -0
- rucio/rse/protocols/rfio.py +145 -0
- rucio/rse/protocols/srm.py +338 -0
- rucio/rse/protocols/ssh.py +414 -0
- rucio/rse/protocols/storm.py +195 -0
- rucio/rse/protocols/webdav.py +594 -0
- rucio/rse/protocols/xrootd.py +302 -0
- rucio/rse/rsemanager.py +881 -0
- rucio/rse/translation.py +260 -0
- rucio/tests/__init__.py +13 -0
- rucio/tests/common.py +280 -0
- rucio/tests/common_server.py +149 -0
- rucio/transfertool/__init__.py +13 -0
- rucio/transfertool/bittorrent.py +200 -0
- rucio/transfertool/bittorrent_driver.py +50 -0
- rucio/transfertool/bittorrent_driver_qbittorrent.py +134 -0
- rucio/transfertool/fts3.py +1600 -0
- rucio/transfertool/fts3_plugins.py +152 -0
- rucio/transfertool/globus.py +201 -0
- rucio/transfertool/globus_library.py +181 -0
- rucio/transfertool/mock.py +89 -0
- rucio/transfertool/transfertool.py +221 -0
- rucio/vcsversion.py +11 -0
- rucio/version.py +45 -0
- rucio/web/__init__.py +13 -0
- rucio/web/rest/__init__.py +13 -0
- rucio/web/rest/flaskapi/__init__.py +13 -0
- rucio/web/rest/flaskapi/authenticated_bp.py +27 -0
- rucio/web/rest/flaskapi/v1/__init__.py +13 -0
- rucio/web/rest/flaskapi/v1/accountlimits.py +236 -0
- rucio/web/rest/flaskapi/v1/accounts.py +1103 -0
- rucio/web/rest/flaskapi/v1/archives.py +102 -0
- rucio/web/rest/flaskapi/v1/auth.py +1644 -0
- rucio/web/rest/flaskapi/v1/common.py +426 -0
- rucio/web/rest/flaskapi/v1/config.py +304 -0
- rucio/web/rest/flaskapi/v1/credentials.py +213 -0
- rucio/web/rest/flaskapi/v1/dids.py +2340 -0
- rucio/web/rest/flaskapi/v1/dirac.py +116 -0
- rucio/web/rest/flaskapi/v1/export.py +75 -0
- rucio/web/rest/flaskapi/v1/heartbeats.py +127 -0
- rucio/web/rest/flaskapi/v1/identities.py +285 -0
- rucio/web/rest/flaskapi/v1/import.py +132 -0
- rucio/web/rest/flaskapi/v1/lifetime_exceptions.py +312 -0
- rucio/web/rest/flaskapi/v1/locks.py +358 -0
- rucio/web/rest/flaskapi/v1/main.py +91 -0
- rucio/web/rest/flaskapi/v1/meta_conventions.py +241 -0
- rucio/web/rest/flaskapi/v1/metrics.py +36 -0
- rucio/web/rest/flaskapi/v1/nongrid_traces.py +97 -0
- rucio/web/rest/flaskapi/v1/ping.py +88 -0
- rucio/web/rest/flaskapi/v1/redirect.py +366 -0
- rucio/web/rest/flaskapi/v1/replicas.py +1894 -0
- rucio/web/rest/flaskapi/v1/requests.py +998 -0
- rucio/web/rest/flaskapi/v1/rses.py +2250 -0
- rucio/web/rest/flaskapi/v1/rules.py +854 -0
- rucio/web/rest/flaskapi/v1/scopes.py +159 -0
- rucio/web/rest/flaskapi/v1/subscriptions.py +650 -0
- rucio/web/rest/flaskapi/v1/templates/auth_crash.html +80 -0
- rucio/web/rest/flaskapi/v1/templates/auth_granted.html +82 -0
- rucio/web/rest/flaskapi/v1/traces.py +137 -0
- rucio/web/rest/flaskapi/v1/types.py +20 -0
- rucio/web/rest/flaskapi/v1/vos.py +278 -0
- rucio/web/rest/main.py +18 -0
- rucio/web/rest/metrics.py +27 -0
- rucio/web/rest/ping.py +27 -0
- rucio-37.0.0rc1.data/data/rucio/etc/alembic.ini.template +71 -0
- rucio-37.0.0rc1.data/data/rucio/etc/alembic_offline.ini.template +74 -0
- rucio-37.0.0rc1.data/data/rucio/etc/globus-config.yml.template +5 -0
- rucio-37.0.0rc1.data/data/rucio/etc/ldap.cfg.template +30 -0
- rucio-37.0.0rc1.data/data/rucio/etc/mail_templates/rule_approval_request.tmpl +38 -0
- rucio-37.0.0rc1.data/data/rucio/etc/mail_templates/rule_approved_admin.tmpl +4 -0
- rucio-37.0.0rc1.data/data/rucio/etc/mail_templates/rule_approved_user.tmpl +17 -0
- rucio-37.0.0rc1.data/data/rucio/etc/mail_templates/rule_denied_admin.tmpl +6 -0
- rucio-37.0.0rc1.data/data/rucio/etc/mail_templates/rule_denied_user.tmpl +17 -0
- rucio-37.0.0rc1.data/data/rucio/etc/mail_templates/rule_ok_notification.tmpl +19 -0
- rucio-37.0.0rc1.data/data/rucio/etc/rse-accounts.cfg.template +25 -0
- rucio-37.0.0rc1.data/data/rucio/etc/rucio.cfg.atlas.client.template +43 -0
- rucio-37.0.0rc1.data/data/rucio/etc/rucio.cfg.template +241 -0
- rucio-37.0.0rc1.data/data/rucio/etc/rucio_multi_vo.cfg.template +217 -0
- rucio-37.0.0rc1.data/data/rucio/requirements.server.txt +297 -0
- rucio-37.0.0rc1.data/data/rucio/tools/bootstrap.py +34 -0
- rucio-37.0.0rc1.data/data/rucio/tools/merge_rucio_configs.py +144 -0
- rucio-37.0.0rc1.data/data/rucio/tools/reset_database.py +40 -0
- rucio-37.0.0rc1.data/scripts/rucio +133 -0
- rucio-37.0.0rc1.data/scripts/rucio-abacus-account +74 -0
- rucio-37.0.0rc1.data/scripts/rucio-abacus-collection-replica +46 -0
- rucio-37.0.0rc1.data/scripts/rucio-abacus-rse +78 -0
- rucio-37.0.0rc1.data/scripts/rucio-admin +97 -0
- rucio-37.0.0rc1.data/scripts/rucio-atropos +60 -0
- rucio-37.0.0rc1.data/scripts/rucio-auditor +206 -0
- rucio-37.0.0rc1.data/scripts/rucio-automatix +50 -0
- rucio-37.0.0rc1.data/scripts/rucio-bb8 +57 -0
- rucio-37.0.0rc1.data/scripts/rucio-cache-client +141 -0
- rucio-37.0.0rc1.data/scripts/rucio-cache-consumer +42 -0
- rucio-37.0.0rc1.data/scripts/rucio-conveyor-finisher +58 -0
- rucio-37.0.0rc1.data/scripts/rucio-conveyor-poller +66 -0
- rucio-37.0.0rc1.data/scripts/rucio-conveyor-preparer +37 -0
- rucio-37.0.0rc1.data/scripts/rucio-conveyor-receiver +44 -0
- rucio-37.0.0rc1.data/scripts/rucio-conveyor-stager +76 -0
- rucio-37.0.0rc1.data/scripts/rucio-conveyor-submitter +139 -0
- rucio-37.0.0rc1.data/scripts/rucio-conveyor-throttler +104 -0
- rucio-37.0.0rc1.data/scripts/rucio-dark-reaper +53 -0
- rucio-37.0.0rc1.data/scripts/rucio-dumper +160 -0
- rucio-37.0.0rc1.data/scripts/rucio-follower +44 -0
- rucio-37.0.0rc1.data/scripts/rucio-hermes +54 -0
- rucio-37.0.0rc1.data/scripts/rucio-judge-cleaner +89 -0
- rucio-37.0.0rc1.data/scripts/rucio-judge-evaluator +137 -0
- rucio-37.0.0rc1.data/scripts/rucio-judge-injector +44 -0
- rucio-37.0.0rc1.data/scripts/rucio-judge-repairer +44 -0
- rucio-37.0.0rc1.data/scripts/rucio-kronos +44 -0
- rucio-37.0.0rc1.data/scripts/rucio-minos +53 -0
- rucio-37.0.0rc1.data/scripts/rucio-minos-temporary-expiration +50 -0
- rucio-37.0.0rc1.data/scripts/rucio-necromancer +120 -0
- rucio-37.0.0rc1.data/scripts/rucio-oauth-manager +63 -0
- rucio-37.0.0rc1.data/scripts/rucio-reaper +83 -0
- rucio-37.0.0rc1.data/scripts/rucio-replica-recoverer +248 -0
- rucio-37.0.0rc1.data/scripts/rucio-rse-decommissioner +66 -0
- rucio-37.0.0rc1.data/scripts/rucio-storage-consistency-actions +74 -0
- rucio-37.0.0rc1.data/scripts/rucio-transmogrifier +77 -0
- rucio-37.0.0rc1.data/scripts/rucio-undertaker +76 -0
- rucio-37.0.0rc1.dist-info/METADATA +92 -0
- rucio-37.0.0rc1.dist-info/RECORD +487 -0
- rucio-37.0.0rc1.dist-info/WHEEL +5 -0
- rucio-37.0.0rc1.dist-info/licenses/AUTHORS.rst +100 -0
- rucio-37.0.0rc1.dist-info/licenses/LICENSE +201 -0
- rucio-37.0.0rc1.dist-info/top_level.txt +1 -0
rucio/core/rse.py
ADDED
|
@@ -0,0 +1,2079 @@
|
|
|
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
|
+
import json
|
|
16
|
+
from datetime import datetime
|
|
17
|
+
from io import StringIO
|
|
18
|
+
from re import match
|
|
19
|
+
from typing import TYPE_CHECKING, Any, Generic, Literal, Optional, TypeVar, Union, overload
|
|
20
|
+
|
|
21
|
+
import sqlalchemy
|
|
22
|
+
from dogpile.cache.api import NoValue
|
|
23
|
+
from sqlalchemy.exc import DatabaseError, IntegrityError, OperationalError
|
|
24
|
+
from sqlalchemy.orm import aliased
|
|
25
|
+
from sqlalchemy.orm.exc import FlushError
|
|
26
|
+
from sqlalchemy.sql.expression import Executable, and_, delete, desc, false, func, or_, select, true
|
|
27
|
+
|
|
28
|
+
from rucio.common import exception, types, utils
|
|
29
|
+
from rucio.common.cache import MemcacheRegion
|
|
30
|
+
from rucio.common.checksum import CHECKSUM_KEY, GLOBALLY_SUPPORTED_CHECKSUMS
|
|
31
|
+
from rucio.common.config import get_lfn2pfn_algorithm_default
|
|
32
|
+
from rucio.common.constants import RSE_ALL_SUPPORTED_PROTOCOL_OPERATIONS, RSE_ATTRS_BOOL, RSE_ATTRS_STR, SUPPORTED_SIGN_URL_SERVICES_LITERAL, RseAttr
|
|
33
|
+
from rucio.common.utils import Availability
|
|
34
|
+
from rucio.core.rse_counter import add_counter, get_counter
|
|
35
|
+
from rucio.db.sqla import models
|
|
36
|
+
from rucio.db.sqla.constants import ReplicaState, RSEType
|
|
37
|
+
from rucio.db.sqla.session import read_session, stream_session, transactional_session
|
|
38
|
+
from rucio.db.sqla.util import temp_table_mngr
|
|
39
|
+
|
|
40
|
+
if TYPE_CHECKING:
|
|
41
|
+
from collections.abc import Iterable, Iterator
|
|
42
|
+
|
|
43
|
+
from sqlalchemy.orm import Session
|
|
44
|
+
from sqlalchemy.sql._typing import _ColumnsClauseArgument
|
|
45
|
+
from typing_extensions import Self
|
|
46
|
+
|
|
47
|
+
T = TypeVar('T', bound="RseData")
|
|
48
|
+
|
|
49
|
+
RSE_SETTINGS = ["continent", "city", "region_code", "country_name", "time_zone", "ISP", "ASN"]
|
|
50
|
+
REGION = MemcacheRegion(expiration_time=900)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class RseData:
|
|
54
|
+
"""
|
|
55
|
+
Helper data class storing rse data grouped in one place.
|
|
56
|
+
"""
|
|
57
|
+
def __init__(
|
|
58
|
+
self,
|
|
59
|
+
id_: str,
|
|
60
|
+
name: Optional[str] = None,
|
|
61
|
+
columns: Optional[dict[str, Any]] = None,
|
|
62
|
+
attributes: Optional[dict[str, Any]] = None,
|
|
63
|
+
info: Optional[types.RSESettingsDict] = None,
|
|
64
|
+
usage: Optional[list[dict[str, Any]]] = None,
|
|
65
|
+
limits: Optional[dict[str, Any]] = None,
|
|
66
|
+
transfer_limits: Optional[dict[str, Any]] = None
|
|
67
|
+
):
|
|
68
|
+
self.id = id_
|
|
69
|
+
self._name = name
|
|
70
|
+
self._columns = columns
|
|
71
|
+
self._attributes = attributes
|
|
72
|
+
self._info = info
|
|
73
|
+
self._usage = usage
|
|
74
|
+
self._limits = limits
|
|
75
|
+
self._transfer_limits = transfer_limits
|
|
76
|
+
|
|
77
|
+
def _get_loaded_attribute(self, attribute_name: str) -> Any:
|
|
78
|
+
attribute = getattr(self, f'_{attribute_name}', None)
|
|
79
|
+
if attribute is None:
|
|
80
|
+
raise ValueError(f'{attribute_name} not loaded for rse {self}')
|
|
81
|
+
return attribute
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def name(self) -> str:
|
|
85
|
+
return self._get_loaded_attribute('name')
|
|
86
|
+
|
|
87
|
+
@name.setter
|
|
88
|
+
def name(self, name: str) -> None:
|
|
89
|
+
self._name = name
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def columns(self) -> dict[str, Any]:
|
|
93
|
+
return self._get_loaded_attribute('columns')
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def attributes(self) -> dict[str, Any]:
|
|
97
|
+
return self._get_loaded_attribute('attributes')
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def info(self) -> types.RSESettingsDict:
|
|
101
|
+
return self._get_loaded_attribute('info')
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def usage(self) -> list[dict[str, Any]]:
|
|
105
|
+
return self._get_loaded_attribute('usage')
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def limits(self) -> dict[str, Any]:
|
|
109
|
+
return self._get_loaded_attribute('limits')
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def transfer_limits(self) -> dict[str, Any]:
|
|
113
|
+
return self._get_loaded_attribute('transfer_limits')
|
|
114
|
+
|
|
115
|
+
def __hash__(self) -> int:
|
|
116
|
+
return hash(self.id)
|
|
117
|
+
|
|
118
|
+
def __repr__(self) -> str:
|
|
119
|
+
if self._name is not None:
|
|
120
|
+
return self._name
|
|
121
|
+
return self.id
|
|
122
|
+
|
|
123
|
+
def __eq__(self, other) -> bool:
|
|
124
|
+
if other is None:
|
|
125
|
+
return False
|
|
126
|
+
return self.id == other.id
|
|
127
|
+
|
|
128
|
+
def is_tape(self) -> bool:
|
|
129
|
+
if self.info['rse_type'] == RSEType.TAPE or self.info['rse_type'] == 'TAPE' or self.attributes.get(RseAttr.STAGING_REQUIRED, False):
|
|
130
|
+
return True
|
|
131
|
+
return False
|
|
132
|
+
|
|
133
|
+
def is_tape_or_staging_required(self) -> bool:
|
|
134
|
+
if self.is_tape() or self.attributes.get(RseAttr.STAGING_REQUIRED, False):
|
|
135
|
+
return True
|
|
136
|
+
return False
|
|
137
|
+
|
|
138
|
+
@read_session
|
|
139
|
+
def ensure_loaded(
|
|
140
|
+
self,
|
|
141
|
+
load_name: bool = False,
|
|
142
|
+
load_columns: bool = False,
|
|
143
|
+
load_attributes: bool = False,
|
|
144
|
+
load_info: bool = False,
|
|
145
|
+
load_usage: bool = False,
|
|
146
|
+
load_limits: bool = False,
|
|
147
|
+
load_transfer_limits: bool = False,
|
|
148
|
+
*,
|
|
149
|
+
session: "Session"
|
|
150
|
+
) -> "Self":
|
|
151
|
+
if self._columns is None and load_columns:
|
|
152
|
+
self._columns = get_rse(rse_id=self.id, session=session)
|
|
153
|
+
self._name = self._columns['rse']
|
|
154
|
+
if self._attributes is None and load_attributes:
|
|
155
|
+
self._attributes = list_rse_attributes(self.id, use_cache=True, session=session)
|
|
156
|
+
if self._info is None and load_info:
|
|
157
|
+
self._info = get_rse_info(self.id, session=session)
|
|
158
|
+
self._name = self._info['rse']
|
|
159
|
+
if self._usage is None and load_usage:
|
|
160
|
+
self._usage = get_rse_usage(rse_id=self.id, session=session)
|
|
161
|
+
if self._limits is None and load_limits:
|
|
162
|
+
self._limits = get_rse_limits(rse_id=self.id, session=session)
|
|
163
|
+
if self._transfer_limits is None and load_transfer_limits:
|
|
164
|
+
self._transfer_limits = get_rse_transfer_limits(rse_id=self.id, session=session)
|
|
165
|
+
if self._name is None and load_name:
|
|
166
|
+
self._name = get_rse_name(rse_id=self.id, session=session)
|
|
167
|
+
return self
|
|
168
|
+
|
|
169
|
+
@staticmethod
|
|
170
|
+
@read_session
|
|
171
|
+
def bulk_load(
|
|
172
|
+
rse_id_to_data: "dict[str, RseData]",
|
|
173
|
+
load_name: bool = False,
|
|
174
|
+
load_columns: bool = False,
|
|
175
|
+
load_attributes: bool = False,
|
|
176
|
+
load_info: bool = False,
|
|
177
|
+
load_usage: bool = False,
|
|
178
|
+
load_limits: bool = False,
|
|
179
|
+
include_deleted: bool = False,
|
|
180
|
+
*,
|
|
181
|
+
session: "Session"
|
|
182
|
+
) -> None:
|
|
183
|
+
"""
|
|
184
|
+
Given a dict of RseData objects indexed by rse_id, ensure that the desired fields are initialised
|
|
185
|
+
in all objects from the input.
|
|
186
|
+
"""
|
|
187
|
+
if len(rse_id_to_data) < 4: # 4 was selected without particular reason as "seems good enough"
|
|
188
|
+
for rse_data in rse_id_to_data.values():
|
|
189
|
+
rse_data.ensure_loaded(
|
|
190
|
+
load_name=load_name,
|
|
191
|
+
load_columns=load_columns,
|
|
192
|
+
load_attributes=load_attributes,
|
|
193
|
+
load_info=load_info,
|
|
194
|
+
load_usage=load_usage,
|
|
195
|
+
load_limits=load_limits,
|
|
196
|
+
session=session
|
|
197
|
+
)
|
|
198
|
+
return
|
|
199
|
+
|
|
200
|
+
rse_ids_to_load = set()
|
|
201
|
+
for rse_id, rse_data in rse_id_to_data.items():
|
|
202
|
+
anything_to_load = any((
|
|
203
|
+
load_name and rse_data._name is None,
|
|
204
|
+
load_columns and rse_data._columns is None,
|
|
205
|
+
load_attributes and rse_data._attributes is None,
|
|
206
|
+
load_info and rse_data._info is None,
|
|
207
|
+
load_usage and rse_data._usage is None,
|
|
208
|
+
load_limits and rse_data._limits is None,
|
|
209
|
+
))
|
|
210
|
+
if anything_to_load:
|
|
211
|
+
rse_ids_to_load.add(rse_id)
|
|
212
|
+
if not rse_ids_to_load:
|
|
213
|
+
# all required fields are already present. Nothing to do
|
|
214
|
+
return
|
|
215
|
+
|
|
216
|
+
temp_table = temp_table_mngr(session).create_id_table()
|
|
217
|
+
session.bulk_insert_mappings(temp_table, ({'id': rse_id} for rse_id in rse_ids_to_load))
|
|
218
|
+
|
|
219
|
+
# We need to ensure that all rses exist and are not deleted. We could check this with a specialized
|
|
220
|
+
# query, but this seems wasteful under normal operation: the caller of the current function probably
|
|
221
|
+
# got the list of RSE IDs from list_rses (or another source which checks for deleted rses).
|
|
222
|
+
#
|
|
223
|
+
# Instead, directly fetch all RSEs, which allows to reduce the number (and complexity) of other queries below
|
|
224
|
+
stmt = select(
|
|
225
|
+
models.RSE
|
|
226
|
+
).join_from(
|
|
227
|
+
temp_table,
|
|
228
|
+
models.RSE,
|
|
229
|
+
models.RSE.id == temp_table.id
|
|
230
|
+
)
|
|
231
|
+
if not include_deleted:
|
|
232
|
+
stmt = stmt.where(
|
|
233
|
+
models.RSE.deleted == false()
|
|
234
|
+
)
|
|
235
|
+
db_rses_by_id = {str(db_rse.id): db_rse for db_rse in session.execute(stmt).scalars()}
|
|
236
|
+
|
|
237
|
+
if len(db_rses_by_id) != len(rse_ids_to_load):
|
|
238
|
+
failed_rse_ids = ', '.join(rse_ids_to_load.difference(db_rses_by_id))
|
|
239
|
+
raise exception.RSENotFound(f"RSE(s) with id(s) '{failed_rse_ids}' cannot be found")
|
|
240
|
+
|
|
241
|
+
if load_attributes:
|
|
242
|
+
for rse_id, attr in _fetch_many_rses_attributes(rse_id_temp_table=temp_table, session=session):
|
|
243
|
+
rse_id_to_data[rse_id]._attributes = attr
|
|
244
|
+
|
|
245
|
+
if load_columns:
|
|
246
|
+
settings_by_id = {}
|
|
247
|
+
if not load_attributes:
|
|
248
|
+
settings_by_id = dict(_fetch_many_rses_attributes(rse_id_temp_table=temp_table,
|
|
249
|
+
keys=RSE_SETTINGS,
|
|
250
|
+
session=session))
|
|
251
|
+
for rse_id, db_rse in db_rses_by_id.items():
|
|
252
|
+
rse_data = rse_id_to_data[rse_id]
|
|
253
|
+
settings = rse_data._attributes if rse_data._attributes is not None else settings_by_id.get(rse_id, {})
|
|
254
|
+
columns = _format_get_rse(db_rse=db_rse, rse_attributes=settings, session=session)
|
|
255
|
+
rse_data._columns = columns
|
|
256
|
+
rse_data._name = columns['rse']
|
|
257
|
+
|
|
258
|
+
if load_info:
|
|
259
|
+
stmt = select(
|
|
260
|
+
temp_table.id,
|
|
261
|
+
models.RSEProtocol
|
|
262
|
+
).outerjoin_from(
|
|
263
|
+
temp_table,
|
|
264
|
+
models.RSEProtocol,
|
|
265
|
+
models.RSEProtocol.rse_id == temp_table.id
|
|
266
|
+
).order_by(
|
|
267
|
+
temp_table.id,
|
|
268
|
+
)
|
|
269
|
+
for rse_id, db_protocols in _group_query_result_by_rse_id(stmt, session=session):
|
|
270
|
+
db_rse = db_rses_by_id[rse_id]
|
|
271
|
+
rse_data = rse_id_to_data[rse_id]
|
|
272
|
+
rse_attributes = rse_data._attributes
|
|
273
|
+
info = _format_get_rse_protocols(rse=db_rse, db_protocols=db_protocols,
|
|
274
|
+
rse_attributes=rse_attributes, session=session)
|
|
275
|
+
rse_data._info = info
|
|
276
|
+
rse_data._name = info['rse']
|
|
277
|
+
|
|
278
|
+
if load_limits:
|
|
279
|
+
stmt = select(
|
|
280
|
+
temp_table.id,
|
|
281
|
+
models.RSELimit
|
|
282
|
+
).outerjoin_from(
|
|
283
|
+
temp_table,
|
|
284
|
+
models.RSELimit,
|
|
285
|
+
models.RSELimit.rse_id == temp_table.id
|
|
286
|
+
).order_by(
|
|
287
|
+
temp_table.id,
|
|
288
|
+
)
|
|
289
|
+
for rse_id, limits_list in _group_query_result_by_rse_id(stmt, session=session):
|
|
290
|
+
rse_id_to_data[rse_id]._limits = {limit.name: limit.value for limit in limits_list}
|
|
291
|
+
|
|
292
|
+
if load_usage:
|
|
293
|
+
stmt = select(
|
|
294
|
+
temp_table.id,
|
|
295
|
+
models.RSEUsage
|
|
296
|
+
).outerjoin_from(
|
|
297
|
+
temp_table,
|
|
298
|
+
models.RSEUsage,
|
|
299
|
+
models.RSEUsage.rse_id == temp_table.id
|
|
300
|
+
).order_by(
|
|
301
|
+
temp_table.id,
|
|
302
|
+
)
|
|
303
|
+
for rse_id, db_usages in _group_query_result_by_rse_id(stmt, session=session):
|
|
304
|
+
usage = _format_get_rse_usage(rse_id=rse_id, db_usages=db_usages, per_account=False, session=session)
|
|
305
|
+
rse_id_to_data[rse_id]._usage = usage
|
|
306
|
+
|
|
307
|
+
if load_name:
|
|
308
|
+
# The name could have been loaded already (when loading columns or info). Skip loading if it's known.
|
|
309
|
+
if not load_columns and not load_info:
|
|
310
|
+
for rse_id in rse_ids_to_load:
|
|
311
|
+
rse_id_to_data[rse_id]._name = db_rses_by_id[rse_id].rse
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
class RseCollection(Generic[T]):
|
|
315
|
+
"""
|
|
316
|
+
Container which keeps track of information loaded from the database for a group of RSEs.
|
|
317
|
+
"""
|
|
318
|
+
|
|
319
|
+
def __init__(self, rse_ids: Optional['Iterable[str]'] = None, rse_data_cls: type[T] = RseData):
|
|
320
|
+
self._rse_data_cls = rse_data_cls
|
|
321
|
+
self.rse_id_to_data_map: dict[str, T] = {}
|
|
322
|
+
if rse_ids is not None:
|
|
323
|
+
for rse_id in rse_ids:
|
|
324
|
+
self.rse_id_to_data_map[rse_id] = self._rse_data_cls(rse_id)
|
|
325
|
+
|
|
326
|
+
def __getitem__(
|
|
327
|
+
self,
|
|
328
|
+
item: str
|
|
329
|
+
) -> "T":
|
|
330
|
+
return self.get_or_create(item)
|
|
331
|
+
|
|
332
|
+
def __setitem__(
|
|
333
|
+
self,
|
|
334
|
+
key: str,
|
|
335
|
+
value: "T"
|
|
336
|
+
) -> None:
|
|
337
|
+
rse_id = key
|
|
338
|
+
rse_data = value
|
|
339
|
+
self.rse_id_to_data_map[rse_id] = rse_data
|
|
340
|
+
|
|
341
|
+
def __contains__(
|
|
342
|
+
self,
|
|
343
|
+
item: Any
|
|
344
|
+
) -> bool:
|
|
345
|
+
if isinstance(item, RseData):
|
|
346
|
+
return item.id in self.rse_id_to_data_map
|
|
347
|
+
if isinstance(item, str):
|
|
348
|
+
return item in self.rse_id_to_data_map
|
|
349
|
+
return False
|
|
350
|
+
|
|
351
|
+
def get(self, rse_id: str) -> "Optional[T]":
|
|
352
|
+
return self.rse_id_to_data_map.get(rse_id)
|
|
353
|
+
|
|
354
|
+
def get_or_create(self, rse_id: str) -> "T":
|
|
355
|
+
rse_data = self.rse_id_to_data_map.get(rse_id)
|
|
356
|
+
if rse_data is None:
|
|
357
|
+
self.rse_id_to_data_map[rse_id] = rse_data = self._rse_data_cls(rse_id)
|
|
358
|
+
return rse_data
|
|
359
|
+
|
|
360
|
+
@transactional_session
|
|
361
|
+
def ensure_loaded(
|
|
362
|
+
self,
|
|
363
|
+
rse_ids: Optional["Iterable[str]"] = None,
|
|
364
|
+
load_name: bool = False,
|
|
365
|
+
load_columns: bool = False,
|
|
366
|
+
load_attributes: bool = False,
|
|
367
|
+
load_info: bool = False,
|
|
368
|
+
load_usage: bool = False,
|
|
369
|
+
load_limits: bool = False,
|
|
370
|
+
include_deleted: bool = False,
|
|
371
|
+
*,
|
|
372
|
+
session: "Session",
|
|
373
|
+
) -> None:
|
|
374
|
+
RseData.bulk_load(
|
|
375
|
+
rse_id_to_data={rse_id: self.get_or_create(rse_id) for rse_id in rse_ids} if rse_ids else self.rse_id_to_data_map,
|
|
376
|
+
load_name=load_name,
|
|
377
|
+
load_columns=load_columns,
|
|
378
|
+
load_attributes=load_attributes,
|
|
379
|
+
load_info=load_info,
|
|
380
|
+
load_usage=load_usage,
|
|
381
|
+
load_limits=load_limits,
|
|
382
|
+
include_deleted=include_deleted,
|
|
383
|
+
session=session,
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
@stream_session
|
|
388
|
+
def _group_query_result_by_rse_id(
|
|
389
|
+
stmt: "Executable",
|
|
390
|
+
*,
|
|
391
|
+
session: "Session"
|
|
392
|
+
) -> 'Iterator[tuple[str, list[Any]]]':
|
|
393
|
+
"""
|
|
394
|
+
Given a sqlalchemy query statement which fetches rows of two elements: (rse_id, object) ordered by rse_id.
|
|
395
|
+
Will execute the query and return objects grouped by rse_id: (rse_id, [object1, object2])
|
|
396
|
+
"""
|
|
397
|
+
|
|
398
|
+
current_rse_id = None
|
|
399
|
+
objects = []
|
|
400
|
+
for rse_id, obj in session.execute(stmt):
|
|
401
|
+
if current_rse_id != rse_id:
|
|
402
|
+
if current_rse_id is not None:
|
|
403
|
+
yield str(current_rse_id), objects
|
|
404
|
+
|
|
405
|
+
current_rse_id = rse_id
|
|
406
|
+
objects = []
|
|
407
|
+
|
|
408
|
+
if obj is not None:
|
|
409
|
+
objects.append(obj)
|
|
410
|
+
|
|
411
|
+
if current_rse_id is not None:
|
|
412
|
+
yield str(current_rse_id), objects
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
@transactional_session
|
|
416
|
+
def add_rse(
|
|
417
|
+
rse: str,
|
|
418
|
+
vo: str = 'def',
|
|
419
|
+
deterministic: bool = True,
|
|
420
|
+
volatile: bool = False,
|
|
421
|
+
city: Optional[str] = None,
|
|
422
|
+
region_code: Optional[str] = None,
|
|
423
|
+
country_name: Optional[str] = None,
|
|
424
|
+
continent: Optional[str] = None,
|
|
425
|
+
time_zone: Optional[str] = None,
|
|
426
|
+
ISP: Optional[str] = None, # noqa: N803
|
|
427
|
+
staging_area: bool = False,
|
|
428
|
+
rse_type: RSEType = RSEType.DISK,
|
|
429
|
+
longitude: Optional[float] = None,
|
|
430
|
+
latitude: Optional[float] = None,
|
|
431
|
+
ASN: Optional[str] = None, # noqa: N803
|
|
432
|
+
availability_read: Optional[bool] = None,
|
|
433
|
+
availability_write: Optional[bool] = None,
|
|
434
|
+
availability_delete: Optional[bool] = None,
|
|
435
|
+
*,
|
|
436
|
+
session: "Session"
|
|
437
|
+
) -> str:
|
|
438
|
+
"""
|
|
439
|
+
Add a rse with the given location name.
|
|
440
|
+
|
|
441
|
+
:param rse: the name of the new rse.
|
|
442
|
+
:param vo: the vo to add the RSE to.
|
|
443
|
+
:param deterministic: Boolean to know if the pfn is generated deterministically.
|
|
444
|
+
:param volatile: Boolean for RSE cache.
|
|
445
|
+
:param city: City for the RSE. Accessed by `locals()`.
|
|
446
|
+
:param region_code: The region code for the RSE. Accessed by `locals()`.
|
|
447
|
+
:param country_name: The country. Accessed by `locals()`.
|
|
448
|
+
:param continent: The continent. Accessed by `locals()`.
|
|
449
|
+
:param time_zone: Timezone. Accessed by `locals()`.
|
|
450
|
+
:param ISP: Internet service provider. Accessed by `locals()`.
|
|
451
|
+
:param staging_area: Staging area.
|
|
452
|
+
:param rse_type: RSE type.
|
|
453
|
+
:param latitude: Latitude coordinate of RSE.
|
|
454
|
+
:param longitude: Longitude coordinate of RSE.
|
|
455
|
+
:param ASN: Access service network. Accessed by `locals()`.
|
|
456
|
+
:param availability_read: If the RSE is readable.
|
|
457
|
+
:param availability_write: If the RSE is writable.
|
|
458
|
+
:param availability_delete: If the RSE is deletable.
|
|
459
|
+
:param session: The database session in use.
|
|
460
|
+
"""
|
|
461
|
+
if isinstance(rse_type, str):
|
|
462
|
+
rse_type = RSEType(rse_type)
|
|
463
|
+
|
|
464
|
+
availability = Availability(availability_read, availability_write, availability_delete).integer
|
|
465
|
+
new_rse = models.RSE(rse=rse, vo=vo, deterministic=deterministic, volatile=volatile,
|
|
466
|
+
staging_area=staging_area, rse_type=rse_type, longitude=longitude,
|
|
467
|
+
latitude=latitude, availability=availability, availability_read=availability_read,
|
|
468
|
+
availability_write=availability_write, availability_delete=availability_delete,
|
|
469
|
+
|
|
470
|
+
# The following fields will be deprecated, they are RSE attributes now.
|
|
471
|
+
# (Still in the code for backwards compatibility)
|
|
472
|
+
city=city, region_code=region_code, country_name=country_name,
|
|
473
|
+
continent=continent, time_zone=time_zone, ISP=ISP, ASN=ASN)
|
|
474
|
+
try:
|
|
475
|
+
new_rse.save(session=session)
|
|
476
|
+
except IntegrityError:
|
|
477
|
+
raise exception.Duplicate(f"RSE '{rse}' already exists!")
|
|
478
|
+
except DatabaseError as error:
|
|
479
|
+
raise exception.RucioException(error.args)
|
|
480
|
+
|
|
481
|
+
# Add rse name as a RSE-Tag
|
|
482
|
+
add_rse_attribute(rse_id=new_rse.id, key=rse, value=True, session=session)
|
|
483
|
+
|
|
484
|
+
for setting in RSE_SETTINGS:
|
|
485
|
+
# The value accessed by locals is defined in the code and it can not be
|
|
486
|
+
# changed by a user request. This thus does not provide a scurity risk.
|
|
487
|
+
setting_value = locals().get(setting, None)
|
|
488
|
+
if setting_value:
|
|
489
|
+
add_rse_attribute(rse_id=new_rse.id, key=setting, value=setting_value, session=session)
|
|
490
|
+
|
|
491
|
+
# Add counter to monitor the space usage
|
|
492
|
+
add_counter(rse_id=new_rse.id, session=session)
|
|
493
|
+
|
|
494
|
+
return new_rse.id
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
@read_session
|
|
498
|
+
def rse_exists(
|
|
499
|
+
rse: str,
|
|
500
|
+
vo: str = 'def',
|
|
501
|
+
include_deleted: bool = False,
|
|
502
|
+
*,
|
|
503
|
+
session: "Session"
|
|
504
|
+
) -> bool:
|
|
505
|
+
"""
|
|
506
|
+
Checks to see if RSE exists.
|
|
507
|
+
|
|
508
|
+
:param rse: Name of the rse.
|
|
509
|
+
:param vo: The VO for the RSE.
|
|
510
|
+
:param session: The database session in use.
|
|
511
|
+
|
|
512
|
+
:returns: True if found, otherwise false.
|
|
513
|
+
"""
|
|
514
|
+
stmt = select(
|
|
515
|
+
models.RSE
|
|
516
|
+
).where(
|
|
517
|
+
and_(models.RSE.rse == rse,
|
|
518
|
+
models.RSE.vo == vo)
|
|
519
|
+
)
|
|
520
|
+
if not include_deleted:
|
|
521
|
+
stmt = stmt.where(models.RSE.deleted == false())
|
|
522
|
+
return True if session.execute(stmt).scalar() else False
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
@transactional_session
|
|
526
|
+
def del_rse(
|
|
527
|
+
rse_id: str,
|
|
528
|
+
*,
|
|
529
|
+
session: "Session"
|
|
530
|
+
) -> None:
|
|
531
|
+
"""
|
|
532
|
+
Disable a rse with the given rse id.
|
|
533
|
+
|
|
534
|
+
:param rse_id: the rse id.
|
|
535
|
+
:param session: The database session in use.
|
|
536
|
+
"""
|
|
537
|
+
|
|
538
|
+
try:
|
|
539
|
+
stmt = select(
|
|
540
|
+
models.RSE
|
|
541
|
+
).where(
|
|
542
|
+
and_(models.RSE.id == rse_id,
|
|
543
|
+
models.RSE.deleted == false())
|
|
544
|
+
)
|
|
545
|
+
db_rse = session.execute(stmt).scalar_one()
|
|
546
|
+
rse_name = db_rse.rse
|
|
547
|
+
if not rse_is_empty(rse_id=rse_id, session=session):
|
|
548
|
+
raise exception.RSEOperationNotSupported('RSE \'%s\' is not empty' % rse_name)
|
|
549
|
+
except sqlalchemy.orm.exc.NoResultFound:
|
|
550
|
+
raise exception.RSENotFound('RSE with id \'%s\' cannot be found' % rse_id)
|
|
551
|
+
db_rse.delete(session=session)
|
|
552
|
+
try:
|
|
553
|
+
del_rse_attribute(rse_id=rse_id, key=rse_name, session=session)
|
|
554
|
+
except exception.RSEAttributeNotFound:
|
|
555
|
+
pass
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
@transactional_session
|
|
559
|
+
def restore_rse(
|
|
560
|
+
rse_id: str,
|
|
561
|
+
*,
|
|
562
|
+
session: "Session"
|
|
563
|
+
) -> None:
|
|
564
|
+
"""
|
|
565
|
+
Restore a rse with the given rse id.
|
|
566
|
+
|
|
567
|
+
:param rse_id: the rse id.
|
|
568
|
+
:param session: The database session in use.
|
|
569
|
+
"""
|
|
570
|
+
|
|
571
|
+
try:
|
|
572
|
+
stmt = select(
|
|
573
|
+
models.RSE
|
|
574
|
+
).where(
|
|
575
|
+
and_(models.RSE.id == rse_id,
|
|
576
|
+
models.RSE.deleted == true())
|
|
577
|
+
)
|
|
578
|
+
db_rse = session.execute(stmt).scalar_one()
|
|
579
|
+
except sqlalchemy.orm.exc.NoResultFound:
|
|
580
|
+
raise exception.RSENotFound('RSE with id \'%s\' cannot be found' % rse_id)
|
|
581
|
+
db_rse.deleted = False
|
|
582
|
+
db_rse.deleted_at = None
|
|
583
|
+
db_rse.save(session=session)
|
|
584
|
+
rse_name = db_rse.rse
|
|
585
|
+
add_rse_attribute(rse_id=rse_id, key=rse_name, value=True, session=session)
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
@read_session
|
|
589
|
+
def rse_is_empty(
|
|
590
|
+
rse_id: str,
|
|
591
|
+
*,
|
|
592
|
+
session: "Session"
|
|
593
|
+
) -> bool:
|
|
594
|
+
"""
|
|
595
|
+
Check if a RSE is empty.
|
|
596
|
+
|
|
597
|
+
:param rse_id: the rse id.
|
|
598
|
+
:param session: the database session in use.
|
|
599
|
+
"""
|
|
600
|
+
|
|
601
|
+
is_empty = False
|
|
602
|
+
try:
|
|
603
|
+
is_empty = get_counter(rse_id, session=session)['bytes'] == 0
|
|
604
|
+
except exception.CounterNotFound:
|
|
605
|
+
is_empty = True
|
|
606
|
+
return is_empty
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
@read_session
|
|
610
|
+
def _format_get_rse(
|
|
611
|
+
db_rse: models.RSE,
|
|
612
|
+
rse_attributes: Optional[dict[str, Any]] = None,
|
|
613
|
+
*,
|
|
614
|
+
session: "Session"
|
|
615
|
+
) -> dict[str, Any]:
|
|
616
|
+
"""
|
|
617
|
+
Given a models.RSE object, return it formatted as expected by callers of get_rse
|
|
618
|
+
"""
|
|
619
|
+
result = db_rse.to_dict()
|
|
620
|
+
result['type'] = db_rse.rse_type
|
|
621
|
+
if rse_attributes is not None:
|
|
622
|
+
rse_settings = {key: rse_attributes[key] for key in RSE_SETTINGS if key in rse_attributes}
|
|
623
|
+
else:
|
|
624
|
+
stmt = select(
|
|
625
|
+
models.RSEAttrAssociation
|
|
626
|
+
).where(
|
|
627
|
+
and_(models.RSEAttrAssociation.rse_id == db_rse.id,
|
|
628
|
+
models.RSEAttrAssociation.key.in_(RSE_SETTINGS)),
|
|
629
|
+
)
|
|
630
|
+
rse_settings = {str(row.key): row.value for row in session.execute(stmt).scalars()}
|
|
631
|
+
result.update(rse_settings)
|
|
632
|
+
return result
|
|
633
|
+
|
|
634
|
+
|
|
635
|
+
@read_session
|
|
636
|
+
def get_rse(
|
|
637
|
+
rse_id: str,
|
|
638
|
+
*,
|
|
639
|
+
session: "Session"
|
|
640
|
+
) -> dict[str, Any]:
|
|
641
|
+
"""
|
|
642
|
+
Get a RSE or raise if it does not exist.
|
|
643
|
+
|
|
644
|
+
:param rse_id: The rse id.
|
|
645
|
+
:param session: The database session in use.
|
|
646
|
+
|
|
647
|
+
:raises RSENotFound: If referred RSE was not found in the database.
|
|
648
|
+
"""
|
|
649
|
+
|
|
650
|
+
try:
|
|
651
|
+
stmt = select(
|
|
652
|
+
models.RSE
|
|
653
|
+
).where(
|
|
654
|
+
and_(models.RSE.deleted == false(),
|
|
655
|
+
models.RSE.id == rse_id)
|
|
656
|
+
)
|
|
657
|
+
return _format_get_rse(session.execute(stmt).scalar_one(), session=session)
|
|
658
|
+
except sqlalchemy.orm.exc.NoResultFound:
|
|
659
|
+
raise exception.RSENotFound('RSE with id \'%s\' cannot be found' % rse_id)
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
@read_session
|
|
663
|
+
def get_rse_id(
|
|
664
|
+
rse: str,
|
|
665
|
+
vo: str = 'def',
|
|
666
|
+
include_deleted: bool = True,
|
|
667
|
+
*,
|
|
668
|
+
session: "Session"
|
|
669
|
+
) -> str:
|
|
670
|
+
"""
|
|
671
|
+
Get a RSE ID or raise if it does not exist.
|
|
672
|
+
|
|
673
|
+
:param rse: the rse name.
|
|
674
|
+
:param session: The database session in use.
|
|
675
|
+
:param include_deleted: Flag to toggle finding rse's marked as deleted.
|
|
676
|
+
|
|
677
|
+
:returns: The rse id.
|
|
678
|
+
|
|
679
|
+
:raises RSENotFound: If referred RSE was not found in the database.
|
|
680
|
+
"""
|
|
681
|
+
|
|
682
|
+
if include_deleted:
|
|
683
|
+
if vo != 'def':
|
|
684
|
+
cache_key = 'rse-id_{}@{}'.format(rse, vo).replace(' ', '.')
|
|
685
|
+
else:
|
|
686
|
+
cache_key = 'rse-id_{}'.format(rse).replace(' ', '.')
|
|
687
|
+
result = REGION.get(cache_key)
|
|
688
|
+
if not isinstance(result, NoValue):
|
|
689
|
+
return result
|
|
690
|
+
|
|
691
|
+
try:
|
|
692
|
+
stmt = select(
|
|
693
|
+
models.RSE.id
|
|
694
|
+
).where(
|
|
695
|
+
and_(models.RSE.rse == rse,
|
|
696
|
+
models.RSE.vo == vo)
|
|
697
|
+
)
|
|
698
|
+
if not include_deleted:
|
|
699
|
+
stmt = stmt.where(models.RSE.deleted == false())
|
|
700
|
+
result = session.execute(stmt).scalar_one()
|
|
701
|
+
except sqlalchemy.orm.exc.NoResultFound:
|
|
702
|
+
raise exception.RSENotFound("RSE '%s' cannot be found in vo '%s'" % (rse, vo))
|
|
703
|
+
|
|
704
|
+
if include_deleted:
|
|
705
|
+
REGION.set(cache_key, result)
|
|
706
|
+
return result
|
|
707
|
+
|
|
708
|
+
|
|
709
|
+
@read_session
|
|
710
|
+
def _get_rse_db_column(
|
|
711
|
+
rse_id: str,
|
|
712
|
+
column: "_ColumnsClauseArgument",
|
|
713
|
+
cache_prefix: str,
|
|
714
|
+
include_deleted: bool = True,
|
|
715
|
+
*,
|
|
716
|
+
session: "Session"
|
|
717
|
+
) -> Any:
|
|
718
|
+
if include_deleted:
|
|
719
|
+
cache_key = '{}_{}'.format(cache_prefix, rse_id)
|
|
720
|
+
result = REGION.get(cache_key)
|
|
721
|
+
if not isinstance(result, NoValue):
|
|
722
|
+
return result
|
|
723
|
+
|
|
724
|
+
try:
|
|
725
|
+
stmt = select(
|
|
726
|
+
column
|
|
727
|
+
).where(
|
|
728
|
+
models.RSE.id == rse_id
|
|
729
|
+
)
|
|
730
|
+
if not include_deleted:
|
|
731
|
+
stmt = stmt.where(models.RSE.deleted == false())
|
|
732
|
+
result = session.execute(stmt).scalar_one()
|
|
733
|
+
except sqlalchemy.orm.exc.NoResultFound:
|
|
734
|
+
raise exception.RSENotFound('RSE with ID \'%s\' cannot be found' % rse_id)
|
|
735
|
+
|
|
736
|
+
if include_deleted:
|
|
737
|
+
REGION.set(cache_key, result)
|
|
738
|
+
return result
|
|
739
|
+
|
|
740
|
+
|
|
741
|
+
@read_session
|
|
742
|
+
def get_rse_name(
|
|
743
|
+
rse_id: str,
|
|
744
|
+
include_deleted: bool = True,
|
|
745
|
+
*,
|
|
746
|
+
session: "Session"
|
|
747
|
+
) -> str:
|
|
748
|
+
"""
|
|
749
|
+
Get a RSE name or raise if it does not exist.
|
|
750
|
+
|
|
751
|
+
:param rse_id: the rse uuid from the database.
|
|
752
|
+
:param session: The database session in use.
|
|
753
|
+
:param include_deleted: Flag to toggle finding rse's marked as deleted.
|
|
754
|
+
|
|
755
|
+
:returns: The rse name.
|
|
756
|
+
|
|
757
|
+
:raises RSENotFound: If referred RSE was not found in the database.
|
|
758
|
+
"""
|
|
759
|
+
return _get_rse_db_column(
|
|
760
|
+
rse_id=rse_id,
|
|
761
|
+
column=models.RSE.rse,
|
|
762
|
+
cache_prefix='rse-name',
|
|
763
|
+
include_deleted=include_deleted,
|
|
764
|
+
session=session
|
|
765
|
+
)
|
|
766
|
+
|
|
767
|
+
|
|
768
|
+
@read_session
|
|
769
|
+
def get_rse_vo(
|
|
770
|
+
rse_id: str,
|
|
771
|
+
include_deleted: bool = True,
|
|
772
|
+
*,
|
|
773
|
+
session: "Session"
|
|
774
|
+
) -> str:
|
|
775
|
+
"""
|
|
776
|
+
Get the VO for a given RSE id.
|
|
777
|
+
|
|
778
|
+
:param rse_id: the rse uuid from the database.
|
|
779
|
+
:param session: the database session in use.
|
|
780
|
+
:param include_deleted: Flag to toggle finding rse's marked as deleted.
|
|
781
|
+
|
|
782
|
+
:returns The vo name.
|
|
783
|
+
|
|
784
|
+
:raises RSENotFound: If referred RSE was not found in database.
|
|
785
|
+
"""
|
|
786
|
+
return _get_rse_db_column(
|
|
787
|
+
rse_id=rse_id,
|
|
788
|
+
column=models.RSE.vo,
|
|
789
|
+
cache_prefix='rse-vo',
|
|
790
|
+
include_deleted=include_deleted,
|
|
791
|
+
session=session
|
|
792
|
+
)
|
|
793
|
+
|
|
794
|
+
|
|
795
|
+
@read_session
|
|
796
|
+
def list_rses(filters: Optional[dict[str, Any]] = None, *, session: "Session") -> list[dict[str, Any]]:
|
|
797
|
+
"""
|
|
798
|
+
Returns a list of all RSEs.
|
|
799
|
+
|
|
800
|
+
:param filters: dictionary of attributes by which the results should be filtered.
|
|
801
|
+
:param session: The database session in use.
|
|
802
|
+
|
|
803
|
+
:returns: a list of dictionaries.
|
|
804
|
+
"""
|
|
805
|
+
|
|
806
|
+
filters = filters or {}
|
|
807
|
+
filters = filters.copy() # Make a copy, so we can pop() without affecting the object `filters` outside this function
|
|
808
|
+
|
|
809
|
+
stmt = select(
|
|
810
|
+
models.RSE
|
|
811
|
+
).where(
|
|
812
|
+
models.RSE.deleted == false()
|
|
813
|
+
)
|
|
814
|
+
if filters:
|
|
815
|
+
if 'availability' in filters and ('availability_read' in filters or 'availability_write' in filters or 'availability_delete' in filters):
|
|
816
|
+
raise exception.InvalidObject('Cannot use availability and read, write, delete filter at the same time.')
|
|
817
|
+
|
|
818
|
+
if 'availability' in filters:
|
|
819
|
+
availability = Availability.from_integer(filters['availability'])
|
|
820
|
+
filters['availability_read'] = availability.read
|
|
821
|
+
filters['availability_write'] = availability.write
|
|
822
|
+
filters['availability_delete'] = availability.delete
|
|
823
|
+
del filters['availability']
|
|
824
|
+
|
|
825
|
+
for (k, v) in filters.items():
|
|
826
|
+
if hasattr(models.RSE, k):
|
|
827
|
+
if k == 'rse_type':
|
|
828
|
+
stmt = stmt.where(getattr(models.RSE, k) == RSEType[v])
|
|
829
|
+
else:
|
|
830
|
+
stmt = stmt.where(getattr(models.RSE, k) == v)
|
|
831
|
+
else:
|
|
832
|
+
attr_assoc_alias = aliased(models.RSEAttrAssociation)
|
|
833
|
+
stmt = stmt.join(
|
|
834
|
+
attr_assoc_alias,
|
|
835
|
+
and_(attr_assoc_alias.rse_id == models.RSE.id,
|
|
836
|
+
attr_assoc_alias.key == k,
|
|
837
|
+
attr_assoc_alias.value == v)
|
|
838
|
+
)
|
|
839
|
+
stmt = stmt.order_by(
|
|
840
|
+
models.RSE.rse
|
|
841
|
+
)
|
|
842
|
+
|
|
843
|
+
return [row.to_dict() for row in session.execute(stmt).scalars()]
|
|
844
|
+
|
|
845
|
+
|
|
846
|
+
@transactional_session
|
|
847
|
+
def add_rse_attribute(
|
|
848
|
+
rse_id: str,
|
|
849
|
+
key: str,
|
|
850
|
+
value: Union[bool, str],
|
|
851
|
+
*,
|
|
852
|
+
session: "Session"
|
|
853
|
+
) -> bool:
|
|
854
|
+
""" Adds a RSE attribute.
|
|
855
|
+
|
|
856
|
+
:param rse_id: the rse id.
|
|
857
|
+
:param key: the key name.
|
|
858
|
+
:param value: the value name.
|
|
859
|
+
:param issuer: The issuer account.
|
|
860
|
+
:param session: The database session in use.
|
|
861
|
+
|
|
862
|
+
:returns: True is successful
|
|
863
|
+
"""
|
|
864
|
+
try:
|
|
865
|
+
new_rse_attr = models.RSEAttrAssociation(rse_id=rse_id, key=key, value=value)
|
|
866
|
+
new_rse_attr = session.merge(new_rse_attr)
|
|
867
|
+
new_rse_attr.save(session=session)
|
|
868
|
+
except IntegrityError:
|
|
869
|
+
rse = get_rse_name(rse_id=rse_id, session=session)
|
|
870
|
+
raise exception.Duplicate(f"RSE attribute '{key}-{value}' for RSE '{rse}' already exists!")
|
|
871
|
+
return True
|
|
872
|
+
|
|
873
|
+
|
|
874
|
+
@transactional_session
|
|
875
|
+
def del_rse_attribute(
|
|
876
|
+
rse_id: str,
|
|
877
|
+
key: str,
|
|
878
|
+
*,
|
|
879
|
+
session: "Session"
|
|
880
|
+
) -> bool:
|
|
881
|
+
"""
|
|
882
|
+
Delete a RSE attribute.
|
|
883
|
+
|
|
884
|
+
:param rse_id: the id of the rse.
|
|
885
|
+
:param key: the attribute key.
|
|
886
|
+
:param session: The database session in use.
|
|
887
|
+
|
|
888
|
+
:return: True if RSE attribute was deleted.
|
|
889
|
+
"""
|
|
890
|
+
try:
|
|
891
|
+
stmt = select(
|
|
892
|
+
models.RSEAttrAssociation
|
|
893
|
+
).where(
|
|
894
|
+
and_(models.RSEAttrAssociation.rse_id == rse_id,
|
|
895
|
+
models.RSEAttrAssociation.key == key)
|
|
896
|
+
)
|
|
897
|
+
rse_attr = session.execute(stmt).scalar_one()
|
|
898
|
+
except sqlalchemy.orm.exc.NoResultFound:
|
|
899
|
+
raise exception.RSEAttributeNotFound('RSE attribute \'%s\' cannot be found' % key)
|
|
900
|
+
rse_attr.delete(session=session)
|
|
901
|
+
return True
|
|
902
|
+
|
|
903
|
+
|
|
904
|
+
@read_session
|
|
905
|
+
def list_rse_attributes(
|
|
906
|
+
rse_id: str,
|
|
907
|
+
use_cache: bool = False,
|
|
908
|
+
*,
|
|
909
|
+
session: "Session"
|
|
910
|
+
) -> dict[str, Union[str, bool]]:
|
|
911
|
+
"""
|
|
912
|
+
List RSE attributes for a RSE.
|
|
913
|
+
|
|
914
|
+
:param rse_id: The RSE id.
|
|
915
|
+
:param use_cache: decides if cache will be used or not
|
|
916
|
+
:param session: The database session in use.
|
|
917
|
+
|
|
918
|
+
:returns: A dictionary with RSE attributes for a RSE.
|
|
919
|
+
"""
|
|
920
|
+
cache_key = 'rse_attributes_%s' % rse_id
|
|
921
|
+
if use_cache:
|
|
922
|
+
value = REGION.get(cache_key)
|
|
923
|
+
|
|
924
|
+
if not isinstance(value, NoValue):
|
|
925
|
+
return value
|
|
926
|
+
|
|
927
|
+
rse_attrs = {}
|
|
928
|
+
|
|
929
|
+
stmt = select(
|
|
930
|
+
models.RSEAttrAssociation
|
|
931
|
+
).where(
|
|
932
|
+
models.RSEAttrAssociation.rse_id == rse_id
|
|
933
|
+
)
|
|
934
|
+
for attr in session.execute(stmt).scalars():
|
|
935
|
+
rse_attrs[attr.key] = attr.value
|
|
936
|
+
|
|
937
|
+
if use_cache:
|
|
938
|
+
REGION.set(cache_key, rse_attrs)
|
|
939
|
+
|
|
940
|
+
return rse_attrs
|
|
941
|
+
|
|
942
|
+
|
|
943
|
+
@stream_session
|
|
944
|
+
def _fetch_many_rses_attributes(
|
|
945
|
+
rse_id_temp_table: Any,
|
|
946
|
+
keys: Optional['Iterable[str]'] = None,
|
|
947
|
+
*,
|
|
948
|
+
session: "Session"
|
|
949
|
+
) -> 'Iterator[tuple[str, dict[str, Any]]]':
|
|
950
|
+
"""
|
|
951
|
+
Given a temporary table pre-filled with RSE IDs, fetch the attributes of these RSEs.
|
|
952
|
+
It's possible to only fetch a subset of attributes by setting the `keys` parameter.
|
|
953
|
+
"""
|
|
954
|
+
|
|
955
|
+
stmt = select(
|
|
956
|
+
rse_id_temp_table.id,
|
|
957
|
+
models.RSEAttrAssociation,
|
|
958
|
+
).outerjoin_from(
|
|
959
|
+
rse_id_temp_table,
|
|
960
|
+
models.RSEAttrAssociation,
|
|
961
|
+
models.RSEAttrAssociation.rse_id == rse_id_temp_table.id
|
|
962
|
+
).order_by(
|
|
963
|
+
rse_id_temp_table.id,
|
|
964
|
+
)
|
|
965
|
+
|
|
966
|
+
if keys:
|
|
967
|
+
stmt = stmt.where(
|
|
968
|
+
models.RSEAttrAssociation.key.in_(keys)
|
|
969
|
+
)
|
|
970
|
+
|
|
971
|
+
for rse_id, attribute_list in _group_query_result_by_rse_id(stmt, session=session):
|
|
972
|
+
yield rse_id, {attr.key: attr.value for attr in attribute_list}
|
|
973
|
+
|
|
974
|
+
|
|
975
|
+
@read_session
|
|
976
|
+
def has_rse_attribute(
|
|
977
|
+
rse_id: str,
|
|
978
|
+
key: str,
|
|
979
|
+
*,
|
|
980
|
+
session: "Session"
|
|
981
|
+
) -> bool:
|
|
982
|
+
"""
|
|
983
|
+
Indicates whether the named key is present for the RSE.
|
|
984
|
+
|
|
985
|
+
:param rse_id: The RSE id.
|
|
986
|
+
:param key: The key for the attribute.
|
|
987
|
+
:param session: The database session in use.
|
|
988
|
+
|
|
989
|
+
:returns: True or False
|
|
990
|
+
"""
|
|
991
|
+
stmt = select(
|
|
992
|
+
models.RSEAttrAssociation.value
|
|
993
|
+
).where(
|
|
994
|
+
and_(models.RSEAttrAssociation.rse_id == rse_id,
|
|
995
|
+
models.RSEAttrAssociation.key == key)
|
|
996
|
+
)
|
|
997
|
+
if session.execute(stmt).scalar():
|
|
998
|
+
return True
|
|
999
|
+
return False
|
|
1000
|
+
|
|
1001
|
+
|
|
1002
|
+
@read_session
|
|
1003
|
+
def get_rses_with_attribute(
|
|
1004
|
+
key: str,
|
|
1005
|
+
*,
|
|
1006
|
+
session: "Session"
|
|
1007
|
+
) -> list[dict[str, Any]]:
|
|
1008
|
+
"""
|
|
1009
|
+
Return all RSEs with a certain attribute.
|
|
1010
|
+
|
|
1011
|
+
:param key: The key for the attribute.
|
|
1012
|
+
:param session: The database session in use.
|
|
1013
|
+
|
|
1014
|
+
:returns: List of rse dictionaries
|
|
1015
|
+
"""
|
|
1016
|
+
rse_list = []
|
|
1017
|
+
|
|
1018
|
+
stmt = select(
|
|
1019
|
+
models.RSE
|
|
1020
|
+
).where(
|
|
1021
|
+
models.RSE.deleted == false()
|
|
1022
|
+
).join(
|
|
1023
|
+
models.RSEAttrAssociation,
|
|
1024
|
+
and_(models.RSEAttrAssociation.rse_id == models.RSE.id,
|
|
1025
|
+
models.RSEAttrAssociation.key == key)
|
|
1026
|
+
)
|
|
1027
|
+
|
|
1028
|
+
for db_rse in session.execute(stmt).scalars():
|
|
1029
|
+
rse_list.append(db_rse.to_dict())
|
|
1030
|
+
|
|
1031
|
+
return rse_list
|
|
1032
|
+
|
|
1033
|
+
|
|
1034
|
+
@read_session
|
|
1035
|
+
def get_rses_with_attribute_value(
|
|
1036
|
+
key: str,
|
|
1037
|
+
value: Union[bool, str],
|
|
1038
|
+
vo: str = 'def',
|
|
1039
|
+
*,
|
|
1040
|
+
session: "Session"
|
|
1041
|
+
) -> list[dict[str, str]]:
|
|
1042
|
+
"""
|
|
1043
|
+
Return all RSEs with a certain attribute.
|
|
1044
|
+
|
|
1045
|
+
:param key: The key for the attribute.
|
|
1046
|
+
:param value: The value for the attribute.
|
|
1047
|
+
:param session: The database session in use.
|
|
1048
|
+
|
|
1049
|
+
:returns: List of rse dictionaries with the rse_id and rse_name
|
|
1050
|
+
"""
|
|
1051
|
+
if vo != 'def':
|
|
1052
|
+
cache_key = 'av-%s-%s@%s' % (key, value, vo)
|
|
1053
|
+
else:
|
|
1054
|
+
cache_key = 'av-%s-%s' % (key, value)
|
|
1055
|
+
|
|
1056
|
+
result = REGION.get(cache_key)
|
|
1057
|
+
if isinstance(result, NoValue):
|
|
1058
|
+
|
|
1059
|
+
rse_list = []
|
|
1060
|
+
|
|
1061
|
+
stmt = select(
|
|
1062
|
+
models.RSE.id,
|
|
1063
|
+
models.RSE.rse,
|
|
1064
|
+
).where(
|
|
1065
|
+
and_(models.RSE.deleted == false(),
|
|
1066
|
+
models.RSE.vo == vo)
|
|
1067
|
+
).join(
|
|
1068
|
+
models.RSEAttrAssociation,
|
|
1069
|
+
and_(models.RSEAttrAssociation.rse_id == models.RSE.id,
|
|
1070
|
+
models.RSEAttrAssociation.key == key,
|
|
1071
|
+
models.RSEAttrAssociation.value == value)
|
|
1072
|
+
)
|
|
1073
|
+
|
|
1074
|
+
for row in session.execute(stmt):
|
|
1075
|
+
rse_list.append({
|
|
1076
|
+
'rse_id': row.id,
|
|
1077
|
+
'rse_name': row.rse
|
|
1078
|
+
})
|
|
1079
|
+
|
|
1080
|
+
REGION.set(cache_key, rse_list)
|
|
1081
|
+
return rse_list
|
|
1082
|
+
|
|
1083
|
+
return result
|
|
1084
|
+
|
|
1085
|
+
|
|
1086
|
+
@overload
|
|
1087
|
+
def get_rse_attribute(rse_id: str, key: Literal['sign_url'], use_cache: bool = True, *, session: "Session") -> Optional[SUPPORTED_SIGN_URL_SERVICES_LITERAL]:
|
|
1088
|
+
...
|
|
1089
|
+
|
|
1090
|
+
|
|
1091
|
+
@overload
|
|
1092
|
+
def get_rse_attribute(rse_id: str, key: Literal['sign_url'], use_cache: bool = True) -> Optional[SUPPORTED_SIGN_URL_SERVICES_LITERAL]:
|
|
1093
|
+
...
|
|
1094
|
+
|
|
1095
|
+
|
|
1096
|
+
@overload
|
|
1097
|
+
def get_rse_attribute(rse_id: str, key: 'RSE_ATTRS_STR', use_cache: bool = True) -> Optional[str]:
|
|
1098
|
+
...
|
|
1099
|
+
|
|
1100
|
+
|
|
1101
|
+
@overload
|
|
1102
|
+
def get_rse_attribute(rse_id: str, key: 'RSE_ATTRS_STR', use_cache: bool = True, *, session: "Session") -> Optional[str]:
|
|
1103
|
+
...
|
|
1104
|
+
|
|
1105
|
+
|
|
1106
|
+
@overload
|
|
1107
|
+
def get_rse_attribute(rse_id: str, key: 'RSE_ATTRS_BOOL', use_cache: bool = True) -> Optional[bool]:
|
|
1108
|
+
...
|
|
1109
|
+
|
|
1110
|
+
|
|
1111
|
+
@overload
|
|
1112
|
+
def get_rse_attribute(rse_id: str, key: 'RSE_ATTRS_BOOL', use_cache: bool = True, *, session: "Session") -> Optional[bool]:
|
|
1113
|
+
...
|
|
1114
|
+
|
|
1115
|
+
|
|
1116
|
+
@read_session
|
|
1117
|
+
def get_rse_attribute(rse_id: str, key: str, use_cache: bool = True, *, session: "Session") -> Optional[Union[str, bool]]:
|
|
1118
|
+
"""
|
|
1119
|
+
Retrieve RSE attribute value. If it is not cached, look it up in the
|
|
1120
|
+
database. If the value exists and is not cached, it will be added to the
|
|
1121
|
+
cache.
|
|
1122
|
+
|
|
1123
|
+
:param rse_id: The RSE id.
|
|
1124
|
+
:param key: The key for the attribute.
|
|
1125
|
+
:param session: The database session in use.
|
|
1126
|
+
|
|
1127
|
+
:returns: The value for the rse attribute, None if it does not exist.
|
|
1128
|
+
"""
|
|
1129
|
+
cache_key = f'rse_attributes_{rse_id}_{key}'
|
|
1130
|
+
if use_cache:
|
|
1131
|
+
value = REGION.get(cache_key)
|
|
1132
|
+
|
|
1133
|
+
if not isinstance(value, NoValue):
|
|
1134
|
+
return value
|
|
1135
|
+
|
|
1136
|
+
stmt = select(
|
|
1137
|
+
models.RSEAttrAssociation.value
|
|
1138
|
+
).where(
|
|
1139
|
+
and_(models.RSEAttrAssociation.rse_id == rse_id,
|
|
1140
|
+
models.RSEAttrAssociation.key == key)
|
|
1141
|
+
)
|
|
1142
|
+
value = session.execute(stmt).scalar_one_or_none()
|
|
1143
|
+
|
|
1144
|
+
if use_cache:
|
|
1145
|
+
REGION.set(cache_key, value)
|
|
1146
|
+
|
|
1147
|
+
return value
|
|
1148
|
+
|
|
1149
|
+
|
|
1150
|
+
def get_rse_supported_checksums_from_attributes(rse_attributes: dict[str, Any]) -> list[str]:
|
|
1151
|
+
"""
|
|
1152
|
+
Parse the RSE attribute defining the checksum supported by the RSE
|
|
1153
|
+
:param rse_attributes: attributes retrieved using list_rse_attributes
|
|
1154
|
+
:returns: A list of the names of supported checksums indicated by the specified attributes.
|
|
1155
|
+
"""
|
|
1156
|
+
return parse_checksum_support_attribute(rse_attributes.get(CHECKSUM_KEY, ''))
|
|
1157
|
+
|
|
1158
|
+
|
|
1159
|
+
def parse_checksum_support_attribute(checksum_attribute: str) -> list[str]:
|
|
1160
|
+
"""
|
|
1161
|
+
Parse the checksum support RSE attribute.
|
|
1162
|
+
:param checksum_attribute: The value of the RSE attribute storing the checksum value
|
|
1163
|
+
|
|
1164
|
+
:returns: The list of checksums supported by the selected RSE.
|
|
1165
|
+
If the list is empty (aka attribute is not set) it returns all the default checksums.
|
|
1166
|
+
Use 'none' to explicitly tell the RSE does not support any checksum algorithm.
|
|
1167
|
+
"""
|
|
1168
|
+
|
|
1169
|
+
if not checksum_attribute:
|
|
1170
|
+
return GLOBALLY_SUPPORTED_CHECKSUMS
|
|
1171
|
+
|
|
1172
|
+
supported_checksum_list = [c.strip() for c in checksum_attribute.split(',') if c.strip()]
|
|
1173
|
+
|
|
1174
|
+
if 'none' in supported_checksum_list:
|
|
1175
|
+
return []
|
|
1176
|
+
else:
|
|
1177
|
+
return supported_checksum_list
|
|
1178
|
+
|
|
1179
|
+
|
|
1180
|
+
@transactional_session
|
|
1181
|
+
def set_rse_usage(
|
|
1182
|
+
rse_id: str,
|
|
1183
|
+
source: str,
|
|
1184
|
+
used: int,
|
|
1185
|
+
free: int,
|
|
1186
|
+
files: Optional[int] = None,
|
|
1187
|
+
*,
|
|
1188
|
+
session: "Session"
|
|
1189
|
+
) -> bool:
|
|
1190
|
+
"""
|
|
1191
|
+
Set RSE usage information.
|
|
1192
|
+
|
|
1193
|
+
:param rse_id: the location id.
|
|
1194
|
+
:param source: The information source, e.g. srm.
|
|
1195
|
+
:param used: the used space in bytes.
|
|
1196
|
+
:param free: the free in bytes.
|
|
1197
|
+
:param files: the number of files
|
|
1198
|
+
:param session: The database session in use.
|
|
1199
|
+
|
|
1200
|
+
:returns: True if successful, otherwise false.
|
|
1201
|
+
"""
|
|
1202
|
+
rse_usage = models.RSEUsage(rse_id=rse_id, source=source, used=used, free=free, files=files)
|
|
1203
|
+
# versioned_session(session)
|
|
1204
|
+
rse_usage = session.merge(rse_usage)
|
|
1205
|
+
rse_usage.save(session=session)
|
|
1206
|
+
|
|
1207
|
+
# rse_usage_history = models.RSEUsage.__history_mapper__.class_(rse_id=rse.id, source=source, used=used, free=free)
|
|
1208
|
+
# rse_usage_history.save(session=session)
|
|
1209
|
+
|
|
1210
|
+
return True
|
|
1211
|
+
|
|
1212
|
+
|
|
1213
|
+
@read_session
|
|
1214
|
+
def get_rse_usage(
|
|
1215
|
+
rse_id: str,
|
|
1216
|
+
source: Optional[str] = None,
|
|
1217
|
+
per_account: bool = False,
|
|
1218
|
+
*,
|
|
1219
|
+
session: "Session"
|
|
1220
|
+
) -> list[dict[str, Any]]:
|
|
1221
|
+
"""
|
|
1222
|
+
get rse usage information.
|
|
1223
|
+
|
|
1224
|
+
:param rse_id: The RSE id.
|
|
1225
|
+
:param source: The information source, e.g. srm.
|
|
1226
|
+
:param session: The database session in use.
|
|
1227
|
+
:param per_account: Boolean whether the usage should be also calculated per account or not.
|
|
1228
|
+
|
|
1229
|
+
:returns: List of RSE usage data.
|
|
1230
|
+
"""
|
|
1231
|
+
|
|
1232
|
+
stmt_rse_usage = select(
|
|
1233
|
+
models.RSEUsage
|
|
1234
|
+
).where(
|
|
1235
|
+
models.RSEUsage.rse_id == rse_id
|
|
1236
|
+
)
|
|
1237
|
+
|
|
1238
|
+
if source:
|
|
1239
|
+
stmt_rse_usage = stmt_rse_usage.where(
|
|
1240
|
+
models.RSEUsage.source == source
|
|
1241
|
+
)
|
|
1242
|
+
|
|
1243
|
+
db_usages = session.execute(stmt_rse_usage).scalars()
|
|
1244
|
+
return _format_get_rse_usage(rse_id=rse_id, db_usages=db_usages, per_account=per_account, session=session)
|
|
1245
|
+
|
|
1246
|
+
|
|
1247
|
+
def _format_get_rse_usage(
|
|
1248
|
+
rse_id: str,
|
|
1249
|
+
db_usages: 'Iterable[models.RSEUsage]',
|
|
1250
|
+
per_account: bool,
|
|
1251
|
+
*,
|
|
1252
|
+
session: "Session"
|
|
1253
|
+
) -> list[dict[str, Any]]:
|
|
1254
|
+
|
|
1255
|
+
usage = list()
|
|
1256
|
+
for db_usage in db_usages:
|
|
1257
|
+
total = (db_usage.free or 0) + (db_usage.used or 0)
|
|
1258
|
+
rse_usage = {'rse_id': rse_id,
|
|
1259
|
+
'source': db_usage.source,
|
|
1260
|
+
'used': db_usage.used,
|
|
1261
|
+
'free': db_usage.free,
|
|
1262
|
+
'total': total,
|
|
1263
|
+
'files': db_usage.files,
|
|
1264
|
+
'updated_at': db_usage.updated_at}
|
|
1265
|
+
if per_account and db_usage.source == 'rucio':
|
|
1266
|
+
stmt_account_usage = select(
|
|
1267
|
+
models.AccountUsage
|
|
1268
|
+
).where(
|
|
1269
|
+
models.AccountUsage.rse_id == rse_id
|
|
1270
|
+
)
|
|
1271
|
+
account_usages = []
|
|
1272
|
+
for row in session.execute(stmt_account_usage).scalars():
|
|
1273
|
+
if row.bytes != 0:
|
|
1274
|
+
percentage = round(float(row.bytes) / float(total) * 100, 2) if total else 0
|
|
1275
|
+
account_usages.append({'used': row.bytes, 'account': row.account, 'percentage': percentage})
|
|
1276
|
+
account_usages.sort(key=lambda x: x['used'], reverse=True)
|
|
1277
|
+
rse_usage['account_usages'] = account_usages
|
|
1278
|
+
usage.append(rse_usage)
|
|
1279
|
+
return usage
|
|
1280
|
+
|
|
1281
|
+
|
|
1282
|
+
@transactional_session
|
|
1283
|
+
def set_rse_limits(rse_id: str, name: str, value: int, *, session: 'Session') -> bool:
|
|
1284
|
+
"""
|
|
1285
|
+
Set RSE limits.
|
|
1286
|
+
|
|
1287
|
+
:param rse_id: The RSE id.
|
|
1288
|
+
:param name: The name of the limit.
|
|
1289
|
+
:param value: The feature value.
|
|
1290
|
+
:param session: The database session in use.
|
|
1291
|
+
|
|
1292
|
+
:returns: True if successful, otherwise false.
|
|
1293
|
+
"""
|
|
1294
|
+
rse_limit = models.RSELimit(rse_id=rse_id, name=name, value=value)
|
|
1295
|
+
rse_limit = session.merge(rse_limit)
|
|
1296
|
+
rse_limit.save(session=session)
|
|
1297
|
+
return True
|
|
1298
|
+
|
|
1299
|
+
|
|
1300
|
+
@read_session
|
|
1301
|
+
def get_rse_limits(rse_id: str, name: Optional[str] = None, *, session: 'Session') -> dict[str, int]:
|
|
1302
|
+
"""
|
|
1303
|
+
Get RSE limits.
|
|
1304
|
+
|
|
1305
|
+
:param rse_id: The RSE id.
|
|
1306
|
+
:param name: A Limit name.
|
|
1307
|
+
|
|
1308
|
+
:returns: A dictionary with the limits {'limit.name': limit.value}.
|
|
1309
|
+
"""
|
|
1310
|
+
|
|
1311
|
+
stmt = select(
|
|
1312
|
+
models.RSELimit
|
|
1313
|
+
).where(
|
|
1314
|
+
models.RSELimit.rse_id == rse_id
|
|
1315
|
+
)
|
|
1316
|
+
if name:
|
|
1317
|
+
stmt = stmt.where(
|
|
1318
|
+
models.RSELimit.name == name
|
|
1319
|
+
)
|
|
1320
|
+
return {limit.name: limit.value for limit in session.execute(stmt).scalars()}
|
|
1321
|
+
|
|
1322
|
+
|
|
1323
|
+
@transactional_session
|
|
1324
|
+
def delete_rse_limits(rse_id: str, name: "Optional[str]" = None, *, session: 'Session') -> None:
|
|
1325
|
+
"""
|
|
1326
|
+
Delete RSE limit.
|
|
1327
|
+
|
|
1328
|
+
:param rse_id: The RSE id.
|
|
1329
|
+
:param name: The name of the limit.
|
|
1330
|
+
"""
|
|
1331
|
+
try:
|
|
1332
|
+
stmt = delete(
|
|
1333
|
+
models.RSELimit
|
|
1334
|
+
).where(
|
|
1335
|
+
models.RSELimit.rse_id == rse_id,
|
|
1336
|
+
)
|
|
1337
|
+
if name is not None:
|
|
1338
|
+
stmt = stmt.where(
|
|
1339
|
+
models.RSELimit.name == name
|
|
1340
|
+
)
|
|
1341
|
+
session.execute(stmt)
|
|
1342
|
+
except IntegrityError as error:
|
|
1343
|
+
raise exception.RucioException(error.args)
|
|
1344
|
+
|
|
1345
|
+
|
|
1346
|
+
def _sanitize_rse_transfer_limit_dict(limit_dict: dict[str, Any]) -> dict[str, Any]:
|
|
1347
|
+
if limit_dict['activity'] == 'all_activities':
|
|
1348
|
+
limit_dict['activity'] = None
|
|
1349
|
+
return limit_dict
|
|
1350
|
+
|
|
1351
|
+
|
|
1352
|
+
@read_session
|
|
1353
|
+
def get_rse_transfer_limits(
|
|
1354
|
+
rse_id: str,
|
|
1355
|
+
activity: Optional[str] = None,
|
|
1356
|
+
*,
|
|
1357
|
+
session: "Session"
|
|
1358
|
+
) -> dict[str, Any]:
|
|
1359
|
+
"""
|
|
1360
|
+
Get RSE transfer limits.
|
|
1361
|
+
|
|
1362
|
+
:param rse_id: The RSE id.
|
|
1363
|
+
:param activity: The activity.
|
|
1364
|
+
|
|
1365
|
+
:returns: A dictionary with the limits {'limit.direction': {'limit.activity': limit}}.
|
|
1366
|
+
"""
|
|
1367
|
+
try:
|
|
1368
|
+
stmt = select(
|
|
1369
|
+
models.TransferLimit
|
|
1370
|
+
).join_from(
|
|
1371
|
+
models.RSETransferLimit,
|
|
1372
|
+
models.TransferLimit,
|
|
1373
|
+
and_(models.RSETransferLimit.limit_id == models.TransferLimit.id,
|
|
1374
|
+
models.RSETransferLimit.rse_id == rse_id)
|
|
1375
|
+
)
|
|
1376
|
+
if activity:
|
|
1377
|
+
stmt = stmt.where(
|
|
1378
|
+
or_(models.TransferLimit.activity == activity,
|
|
1379
|
+
models.TransferLimit.activity == 'all_activities')
|
|
1380
|
+
)
|
|
1381
|
+
|
|
1382
|
+
limits = {}
|
|
1383
|
+
for limit in session.execute(stmt).scalars():
|
|
1384
|
+
limit_dict = _sanitize_rse_transfer_limit_dict(limit.to_dict())
|
|
1385
|
+
limits.setdefault(limit_dict['direction'], {}).setdefault(limit_dict['activity'], limit_dict)
|
|
1386
|
+
|
|
1387
|
+
return limits
|
|
1388
|
+
except IntegrityError as error:
|
|
1389
|
+
raise exception.RucioException(error.args)
|
|
1390
|
+
|
|
1391
|
+
|
|
1392
|
+
@stream_session
|
|
1393
|
+
def list_rse_usage_history(
|
|
1394
|
+
rse_id: str,
|
|
1395
|
+
source: Optional[str] = None,
|
|
1396
|
+
*,
|
|
1397
|
+
session: "Session"
|
|
1398
|
+
) -> 'Iterator[dict[str, Any]]':
|
|
1399
|
+
"""
|
|
1400
|
+
List RSE usage history information.
|
|
1401
|
+
|
|
1402
|
+
:param rse_id: The RSE id.
|
|
1403
|
+
:param source: The source of the usage information (srm, rucio).
|
|
1404
|
+
:param session: The database session in use.
|
|
1405
|
+
|
|
1406
|
+
:returns: A list of historic RSE usage.
|
|
1407
|
+
"""
|
|
1408
|
+
stmt = select(
|
|
1409
|
+
models.RSEUsageHistory
|
|
1410
|
+
).where(
|
|
1411
|
+
models.RSEUsageHistory.rse_id == rse_id
|
|
1412
|
+
).order_by(
|
|
1413
|
+
desc(models.RSEUsageHistory.updated_at)
|
|
1414
|
+
)
|
|
1415
|
+
if source:
|
|
1416
|
+
stmt = stmt.where(
|
|
1417
|
+
models.RSEUsageHistory.source == source
|
|
1418
|
+
)
|
|
1419
|
+
|
|
1420
|
+
rse = get_rse_name(rse_id=rse_id, session=session)
|
|
1421
|
+
for usage in session.execute(stmt).yield_per(5).scalars():
|
|
1422
|
+
yield ({'rse_id': rse_id,
|
|
1423
|
+
'rse': rse,
|
|
1424
|
+
'source': usage.source,
|
|
1425
|
+
'used': usage.used if usage.used else 0,
|
|
1426
|
+
'total': usage.used if usage.used else 0 + usage.free if usage.free else 0,
|
|
1427
|
+
'free': usage.free if usage.free else 0,
|
|
1428
|
+
'updated_at': usage.updated_at})
|
|
1429
|
+
|
|
1430
|
+
|
|
1431
|
+
@transactional_session
|
|
1432
|
+
def add_protocol(
|
|
1433
|
+
rse_id: str,
|
|
1434
|
+
parameter: dict[str, Any],
|
|
1435
|
+
*,
|
|
1436
|
+
session: "Session"
|
|
1437
|
+
) -> models.RSEProtocol:
|
|
1438
|
+
"""
|
|
1439
|
+
Add a protocol to an existing RSE.
|
|
1440
|
+
|
|
1441
|
+
:param rse_id: the ID of the new RSE.
|
|
1442
|
+
:param parameter: parameters of the new protocol entry.
|
|
1443
|
+
:param session: The database session in use.
|
|
1444
|
+
|
|
1445
|
+
:raises RSENotFound: If RSE is not found.
|
|
1446
|
+
:raises RSEOperationNotSupported: If no scheme supported the requested operation for the given RSE.
|
|
1447
|
+
:raises RSEProtocolDomainNotSupported: If an undefined domain was provided.
|
|
1448
|
+
:raises RSEProtocolPriorityError: If the provided priority for the scheme is to big or below zero.
|
|
1449
|
+
:raises Duplicate: If scheme with identifier, hostname and port already exists
|
|
1450
|
+
for the given RSE.
|
|
1451
|
+
"""
|
|
1452
|
+
|
|
1453
|
+
rse = ""
|
|
1454
|
+
try:
|
|
1455
|
+
rse = get_rse_name(rse_id=rse_id, session=session, include_deleted=False)
|
|
1456
|
+
except exception.RSENotFound:
|
|
1457
|
+
raise exception.RSENotFound('RSE id \'%s\' not found' % rse_id)
|
|
1458
|
+
# Insert new protocol entry
|
|
1459
|
+
parameter['rse_id'] = rse_id
|
|
1460
|
+
|
|
1461
|
+
# Default values
|
|
1462
|
+
parameter['port'] = parameter.get('port', 0)
|
|
1463
|
+
parameter['hostname'] = parameter.get('hostname', 'localhost')
|
|
1464
|
+
|
|
1465
|
+
# Transform nested domains to match DB schema e.g. [domains][lan][read] => [read_lan]
|
|
1466
|
+
if 'domains' in parameter:
|
|
1467
|
+
for domain in parameter['domains']:
|
|
1468
|
+
if domain not in utils.rse_supported_protocol_domains():
|
|
1469
|
+
raise exception.RSEProtocolDomainNotSupported(f"The protocol domain '{domain}' is not defined in the schema.")
|
|
1470
|
+
for op in parameter['domains'][domain]:
|
|
1471
|
+
if op not in RSE_ALL_SUPPORTED_PROTOCOL_OPERATIONS:
|
|
1472
|
+
raise exception.RSEOperationNotSupported(f"Operation '{op}' not defined in schema.")
|
|
1473
|
+
op_name = op if op.startswith('third_party_copy') else f'{op}_{domain}'.lower()
|
|
1474
|
+
priority = parameter['domains'][domain][op]
|
|
1475
|
+
if (type(priority) is not int or priority < 0) and priority is not None:
|
|
1476
|
+
raise exception.RSEProtocolPriorityError(f"The provided priority ({priority}) for operation '{op}' in domain '{domain}' is not supported.")
|
|
1477
|
+
parameter[op_name] = priority
|
|
1478
|
+
del parameter['domains']
|
|
1479
|
+
|
|
1480
|
+
if ('extended_attributes' in parameter) and parameter['extended_attributes']:
|
|
1481
|
+
try:
|
|
1482
|
+
parameter['extended_attributes'] = json.dumps(parameter['extended_attributes'], separators=(',', ':'))
|
|
1483
|
+
except ValueError:
|
|
1484
|
+
pass # String is not JSON
|
|
1485
|
+
|
|
1486
|
+
if parameter['scheme'] == 'srm':
|
|
1487
|
+
if ('extended_attributes' not in parameter) or ('web_service_path' not in parameter['extended_attributes']):
|
|
1488
|
+
raise exception.InvalidObject('Missing values! For SRM, extended_attributes and web_service_path must be specified')
|
|
1489
|
+
|
|
1490
|
+
try:
|
|
1491
|
+
new_protocol = models.RSEProtocol()
|
|
1492
|
+
new_protocol.update(parameter)
|
|
1493
|
+
new_protocol.save(session=session)
|
|
1494
|
+
except (IntegrityError, FlushError, OperationalError) as error:
|
|
1495
|
+
if ('UNIQUE constraint failed' in error.args[0]) or ('conflicts with persistent instance' in error.args[0]) \
|
|
1496
|
+
or match('.*IntegrityError.*ORA-00001: unique constraint.*RSE_PROTOCOLS_PK.*violated.*', error.args[0]) \
|
|
1497
|
+
or match('.*IntegrityError.*1062.*Duplicate entry.*for key.*', error.args[0]) \
|
|
1498
|
+
or match('.*IntegrityError.*duplicate key value violates unique constraint.*', error.args[0]) \
|
|
1499
|
+
or match('.*UniqueViolation.*duplicate key value violates unique constraint.*', error.args[0]) \
|
|
1500
|
+
or match('.*IntegrityError.*columns.*are not unique.*', error.args[0]):
|
|
1501
|
+
raise exception.Duplicate('Protocol \'%s\' on port %s already registered for \'%s\' with hostname \'%s\'.' % (parameter['scheme'], parameter['port'], rse, parameter['hostname']))
|
|
1502
|
+
elif 'may not be NULL' in error.args[0] \
|
|
1503
|
+
or match('.*IntegrityError.*ORA-01400: cannot insert NULL into.*RSE_PROTOCOLS.*IMPL.*', error.args[0]) \
|
|
1504
|
+
or match('.*IntegrityError.*Column.*cannot be null.*', error.args[0]) \
|
|
1505
|
+
or match('.*IntegrityError.*null value in column.*violates not-null constraint.*', error.args[0]) \
|
|
1506
|
+
or match('.*IntegrityError.*NOT NULL constraint failed.*', error.args[0]) \
|
|
1507
|
+
or match('.*NotNullViolation.*null value in column.*violates not-null constraint.*', error.args[0]) \
|
|
1508
|
+
or match('.*OperationalError.*cannot be null.*', error.args[0]):
|
|
1509
|
+
raise exception.InvalidObject('Missing values!')
|
|
1510
|
+
|
|
1511
|
+
raise exception.RucioException(error.args)
|
|
1512
|
+
return new_protocol
|
|
1513
|
+
|
|
1514
|
+
|
|
1515
|
+
@read_session
|
|
1516
|
+
def get_rse_protocols(
|
|
1517
|
+
rse_id: str,
|
|
1518
|
+
schemes: Optional[list[str]] = None,
|
|
1519
|
+
*,
|
|
1520
|
+
session: "Session"
|
|
1521
|
+
) -> types.RSESettingsDict:
|
|
1522
|
+
"""
|
|
1523
|
+
Returns protocol information. Parameter combinations are: (operation OR default) XOR scheme.
|
|
1524
|
+
|
|
1525
|
+
:param rse_id: The id of the rse.
|
|
1526
|
+
:param schemes: a list of schemes to filter by.
|
|
1527
|
+
:param session: The database session.
|
|
1528
|
+
|
|
1529
|
+
:returns: A dict with RSE information and supported protocols
|
|
1530
|
+
|
|
1531
|
+
:raises RSENotFound: If RSE is not found.
|
|
1532
|
+
"""
|
|
1533
|
+
|
|
1534
|
+
_rse = get_rse(rse_id=rse_id, session=session)
|
|
1535
|
+
if not _rse:
|
|
1536
|
+
raise exception.RSENotFound('RSE with id \'%s\' not found' % rse_id)
|
|
1537
|
+
|
|
1538
|
+
terms = [models.RSEProtocol.rse_id == rse_id]
|
|
1539
|
+
if schemes:
|
|
1540
|
+
if not isinstance(schemes, list):
|
|
1541
|
+
schemes = [schemes]
|
|
1542
|
+
terms.extend([models.RSEProtocol.scheme.in_(schemes)])
|
|
1543
|
+
|
|
1544
|
+
stmt = select(
|
|
1545
|
+
models.RSEProtocol
|
|
1546
|
+
).where(
|
|
1547
|
+
*terms
|
|
1548
|
+
)
|
|
1549
|
+
|
|
1550
|
+
_protocols = session.execute(stmt).scalars().all()
|
|
1551
|
+
return _format_get_rse_protocols(rse=_rse, db_protocols=_protocols, session=session)
|
|
1552
|
+
|
|
1553
|
+
|
|
1554
|
+
def _format_get_rse_protocols(
|
|
1555
|
+
rse: "models.RSE | dict[str, Any]",
|
|
1556
|
+
db_protocols: 'Iterable[models.RSEProtocol]',
|
|
1557
|
+
rse_attributes: Optional[dict[str, Any]] = None,
|
|
1558
|
+
*,
|
|
1559
|
+
session: "Session"
|
|
1560
|
+
) -> types.RSESettingsDict:
|
|
1561
|
+
_rse = rse
|
|
1562
|
+
if rse_attributes:
|
|
1563
|
+
lfn2pfn_algorithm = rse_attributes.get(RseAttr.LFN2PFN_ALGORITHM)
|
|
1564
|
+
else:
|
|
1565
|
+
lfn2pfn_algorithm = get_rse_attribute(_rse['id'], RseAttr.LFN2PFN_ALGORITHM, session=session)
|
|
1566
|
+
# Resolve LFN2PFN default algorithm as soon as possible. This way, we can send back the actual
|
|
1567
|
+
# algorithm name in response to REST queries.
|
|
1568
|
+
if not lfn2pfn_algorithm:
|
|
1569
|
+
lfn2pfn_algorithm = get_lfn2pfn_algorithm_default()
|
|
1570
|
+
|
|
1571
|
+
# Copy verify_checksum from the attributes, later: assume True if not specified
|
|
1572
|
+
if rse_attributes:
|
|
1573
|
+
verify_checksum = rse_attributes.get(RseAttr.VERIFY_CHECKSUM)
|
|
1574
|
+
else:
|
|
1575
|
+
verify_checksum = get_rse_attribute(_rse['id'], RseAttr.VERIFY_CHECKSUM, session=session)
|
|
1576
|
+
|
|
1577
|
+
# Copy sign_url from the attributes
|
|
1578
|
+
if rse_attributes:
|
|
1579
|
+
sign_url = rse_attributes.get(RseAttr.SIGN_URL)
|
|
1580
|
+
else:
|
|
1581
|
+
sign_url = get_rse_attribute(_rse['id'], RseAttr.SIGN_URL, session=session)
|
|
1582
|
+
|
|
1583
|
+
info = {'availability_delete': _rse['availability_delete'],
|
|
1584
|
+
'availability_read': _rse['availability_read'],
|
|
1585
|
+
'availability_write': _rse['availability_write'],
|
|
1586
|
+
'credentials': None,
|
|
1587
|
+
'deterministic': _rse['deterministic'],
|
|
1588
|
+
'domain': utils.rse_supported_protocol_domains(),
|
|
1589
|
+
'id': _rse['id'],
|
|
1590
|
+
'lfn2pfn_algorithm': lfn2pfn_algorithm,
|
|
1591
|
+
'protocols': list(),
|
|
1592
|
+
'qos_class': _rse['qos_class'],
|
|
1593
|
+
'rse': _rse['rse'],
|
|
1594
|
+
'rse_type': _rse['rse_type'].name,
|
|
1595
|
+
'sign_url': sign_url,
|
|
1596
|
+
'staging_area': _rse['staging_area'],
|
|
1597
|
+
'verify_checksum': verify_checksum if verify_checksum is not None else True,
|
|
1598
|
+
'volatile': _rse['volatile']}
|
|
1599
|
+
|
|
1600
|
+
for op in RSE_ALL_SUPPORTED_PROTOCOL_OPERATIONS:
|
|
1601
|
+
info['%s_protocol' % op] = 1 # 1 indicates the default protocol
|
|
1602
|
+
|
|
1603
|
+
for row in db_protocols:
|
|
1604
|
+
p = {'hostname': row.hostname,
|
|
1605
|
+
'scheme': row.scheme,
|
|
1606
|
+
'port': row.port,
|
|
1607
|
+
'prefix': row.prefix if row.prefix is not None else '',
|
|
1608
|
+
'impl': row.impl,
|
|
1609
|
+
'domains': {
|
|
1610
|
+
'lan': {'read': row.read_lan,
|
|
1611
|
+
'write': row.write_lan,
|
|
1612
|
+
'delete': row.delete_lan},
|
|
1613
|
+
'wan': {'read': row.read_wan,
|
|
1614
|
+
'write': row.write_wan,
|
|
1615
|
+
'delete': row.delete_wan,
|
|
1616
|
+
'third_party_copy_read': row.third_party_copy_read,
|
|
1617
|
+
'third_party_copy_write': row.third_party_copy_write}
|
|
1618
|
+
},
|
|
1619
|
+
'extended_attributes': row.extended_attributes}
|
|
1620
|
+
|
|
1621
|
+
try:
|
|
1622
|
+
p['extended_attributes'] = json.load(StringIO(p['extended_attributes']))
|
|
1623
|
+
except ValueError:
|
|
1624
|
+
pass # If value is not a JSON string
|
|
1625
|
+
|
|
1626
|
+
info['protocols'].append(p)
|
|
1627
|
+
info['protocols'] = sorted(info['protocols'], key=lambda p: (p['hostname'], p['scheme'], p['port']))
|
|
1628
|
+
return info
|
|
1629
|
+
|
|
1630
|
+
|
|
1631
|
+
@read_session
|
|
1632
|
+
def get_rse_info(rse_id: str, *, session: "Session") -> types.RSESettingsDict:
|
|
1633
|
+
"""
|
|
1634
|
+
For historical reasons, related to usage of rsemanager, "rse_info" is equivalent to
|
|
1635
|
+
a cached call to get_rse_protocols without any schemes set.
|
|
1636
|
+
|
|
1637
|
+
:param rse_id: The id of the rse.
|
|
1638
|
+
:param session: The database session.
|
|
1639
|
+
:returns: A dict with RSE information and supported protocols
|
|
1640
|
+
"""
|
|
1641
|
+
key = 'rse_info_%s' % rse_id
|
|
1642
|
+
result = REGION.get(key)
|
|
1643
|
+
if isinstance(result, NoValue):
|
|
1644
|
+
result = get_rse_protocols(rse_id=rse_id, session=session)
|
|
1645
|
+
REGION.set(key, result)
|
|
1646
|
+
return result
|
|
1647
|
+
|
|
1648
|
+
|
|
1649
|
+
@transactional_session
|
|
1650
|
+
def update_protocols(
|
|
1651
|
+
rse_id: str,
|
|
1652
|
+
scheme: str,
|
|
1653
|
+
data: dict[str, Any],
|
|
1654
|
+
hostname: str,
|
|
1655
|
+
port: int,
|
|
1656
|
+
*,
|
|
1657
|
+
session: "Session"
|
|
1658
|
+
) -> None:
|
|
1659
|
+
"""
|
|
1660
|
+
Update an existing protocol entry for an RSE.
|
|
1661
|
+
|
|
1662
|
+
:param rse_id: the ID of the RSE.
|
|
1663
|
+
:param scheme: Protocol identifier.
|
|
1664
|
+
:param data: Dict with new values (keys must match column names in the database).
|
|
1665
|
+
:param hostname: Hostname defined for the scheme, used if more than one scheme
|
|
1666
|
+
is registered with the same identifier.
|
|
1667
|
+
:param port: The port registered for the hostname, used if more than one scheme
|
|
1668
|
+
is registered with the same identifier and hostname.
|
|
1669
|
+
:param session: The database session in use.
|
|
1670
|
+
|
|
1671
|
+
:raises RSENotFound: If RSE is not found.
|
|
1672
|
+
:raises RSEProtocolNotSupported: If no matching protocol was found for the given RSE.
|
|
1673
|
+
:raises RSEOperationNotSupported: If no protocol supported the requested operation for the given RSE.
|
|
1674
|
+
:raises RSEProtocolDomainNotSupported: If an undefined domain was provided.
|
|
1675
|
+
:raises RSEProtocolPriorityError: If the provided priority for the protocol is too big or below zero.
|
|
1676
|
+
:raises KeyNotFound: Invalid data for update provided.
|
|
1677
|
+
:raises Duplicate: If protocol with identifier, hostname and port already exists
|
|
1678
|
+
for the given RSE.
|
|
1679
|
+
"""
|
|
1680
|
+
|
|
1681
|
+
# Transform nested domains to match DB schema e.g. [domains][lan][read] => [read_lan]
|
|
1682
|
+
if 'domains' in data:
|
|
1683
|
+
for domain in data['domains']:
|
|
1684
|
+
if domain not in utils.rse_supported_protocol_domains():
|
|
1685
|
+
raise exception.RSEProtocolDomainNotSupported(f"The protocol domain '{domain}' is not defined in the schema.")
|
|
1686
|
+
for op in data['domains'][domain]:
|
|
1687
|
+
if op not in RSE_ALL_SUPPORTED_PROTOCOL_OPERATIONS:
|
|
1688
|
+
raise exception.RSEOperationNotSupported(f"Operation '{op}' not defined in schema.")
|
|
1689
|
+
op_name = op if op.startswith('third_party_copy') else f'{op}_{domain}'.lower()
|
|
1690
|
+
priority = data['domains'][domain][op]
|
|
1691
|
+
if (type(priority) is not int or priority < 0) and priority is not None:
|
|
1692
|
+
raise exception.RSEProtocolPriorityError(f"The provided priority ({priority}) for operation '{op}' in domain '{domain}' is not supported.")
|
|
1693
|
+
data[op_name] = priority
|
|
1694
|
+
del data['domains']
|
|
1695
|
+
|
|
1696
|
+
if 'extended_attributes' in data:
|
|
1697
|
+
try:
|
|
1698
|
+
data['extended_attributes'] = json.dumps(data['extended_attributes'], separators=(',', ':'))
|
|
1699
|
+
except ValueError:
|
|
1700
|
+
pass # String is not JSON
|
|
1701
|
+
|
|
1702
|
+
try:
|
|
1703
|
+
rse = get_rse_name(rse_id=rse_id, session=session, include_deleted=False)
|
|
1704
|
+
except exception.RSENotFound:
|
|
1705
|
+
raise exception.RSENotFound('RSE with id \'%s\' not found' % rse_id)
|
|
1706
|
+
|
|
1707
|
+
terms = [models.RSEProtocol.rse_id == rse_id,
|
|
1708
|
+
models.RSEProtocol.scheme == scheme,
|
|
1709
|
+
models.RSEProtocol.hostname == hostname,
|
|
1710
|
+
models.RSEProtocol.port == port]
|
|
1711
|
+
|
|
1712
|
+
try:
|
|
1713
|
+
stmt = select(
|
|
1714
|
+
models.RSEProtocol
|
|
1715
|
+
).where(
|
|
1716
|
+
*terms
|
|
1717
|
+
)
|
|
1718
|
+
up = session.execute(stmt).scalar()
|
|
1719
|
+
if up is None:
|
|
1720
|
+
msg = 'RSE \'%s\' does not support protocol \'%s\' for hostname \'%s\' on port \'%s\'' % (rse, scheme, hostname, port)
|
|
1721
|
+
raise exception.RSEProtocolNotSupported(msg)
|
|
1722
|
+
up.update(data, flush=True, session=session)
|
|
1723
|
+
except (IntegrityError, OperationalError) as error:
|
|
1724
|
+
if 'UNIQUE'.lower() in error.args[0].lower() or 'Duplicate' in error.args[0]: # Covers SQLite, Oracle and MySQL error
|
|
1725
|
+
raise exception.Duplicate('Protocol \'%s\' on port %s already registered for \'%s\' with hostname \'%s\'.' % (scheme, port, rse, hostname))
|
|
1726
|
+
elif 'may not be NULL' in error.args[0] or "cannot be null" in error.args[0]:
|
|
1727
|
+
raise exception.InvalidObject('Missing values: %s' % error.args[0])
|
|
1728
|
+
raise error
|
|
1729
|
+
except DatabaseError as error:
|
|
1730
|
+
if match('.*DatabaseError.*ORA-01407: cannot update .*RSE_PROTOCOLS.*IMPL.*to NULL.*', error.args[0]):
|
|
1731
|
+
raise exception.InvalidObject('Invalid values !')
|
|
1732
|
+
raise error
|
|
1733
|
+
|
|
1734
|
+
|
|
1735
|
+
@transactional_session
|
|
1736
|
+
def del_protocols(
|
|
1737
|
+
rse_id: str,
|
|
1738
|
+
scheme: str,
|
|
1739
|
+
hostname: Optional[str] = None,
|
|
1740
|
+
port: Optional[int] = None,
|
|
1741
|
+
*,
|
|
1742
|
+
session: "Session"
|
|
1743
|
+
) -> None:
|
|
1744
|
+
"""
|
|
1745
|
+
Delete one or more existing protocol entries for an RSE.
|
|
1746
|
+
|
|
1747
|
+
:param rse_id: the ID of the RSE.
|
|
1748
|
+
:param scheme: Protocol identifier.
|
|
1749
|
+
:param hostname: Hostname defined for the scheme, used if more than one scheme
|
|
1750
|
+
is registered with the same identifier.
|
|
1751
|
+
:param port: The port registered for the hostname, used if more than one scheme
|
|
1752
|
+
is registered with the same identifier and hostname.
|
|
1753
|
+
:param session: The database session in use.
|
|
1754
|
+
|
|
1755
|
+
:raises RSENotFound: If RSE is not found.
|
|
1756
|
+
:raises RSEProtocolNotSupported: If no matching scheme was found for the given RSE.
|
|
1757
|
+
"""
|
|
1758
|
+
try:
|
|
1759
|
+
rse_name = get_rse_name(rse_id=rse_id, session=session, include_deleted=False)
|
|
1760
|
+
except exception.RSENotFound:
|
|
1761
|
+
raise exception.RSENotFound('RSE \'%s\' not found' % rse_id)
|
|
1762
|
+
terms = [models.RSEProtocol.rse_id == rse_id, models.RSEProtocol.scheme == scheme]
|
|
1763
|
+
if hostname is not None:
|
|
1764
|
+
terms.append(models.RSEProtocol.hostname == hostname)
|
|
1765
|
+
if port is not None:
|
|
1766
|
+
terms.append(models.RSEProtocol.port == port)
|
|
1767
|
+
stmt = select(
|
|
1768
|
+
models.RSEProtocol
|
|
1769
|
+
).where(
|
|
1770
|
+
*terms
|
|
1771
|
+
)
|
|
1772
|
+
p = session.execute(stmt).scalars().all()
|
|
1773
|
+
|
|
1774
|
+
if not p:
|
|
1775
|
+
msg = 'RSE \'%s\' does not support protocol \'%s\'' % (rse_name, scheme)
|
|
1776
|
+
msg += ' for hostname \'%s\'' % hostname if hostname else ''
|
|
1777
|
+
msg += ' on port \'%s\'' % port if port else ''
|
|
1778
|
+
raise exception.RSEProtocolNotSupported(msg)
|
|
1779
|
+
|
|
1780
|
+
for row in p:
|
|
1781
|
+
row.delete(session=session)
|
|
1782
|
+
|
|
1783
|
+
|
|
1784
|
+
MUTABLE_RSE_PROPERTIES = {
|
|
1785
|
+
'name',
|
|
1786
|
+
'availability_read',
|
|
1787
|
+
'availability_write',
|
|
1788
|
+
'availability_delete',
|
|
1789
|
+
'latitude',
|
|
1790
|
+
'longitude',
|
|
1791
|
+
'time_zone',
|
|
1792
|
+
'rse_type',
|
|
1793
|
+
'volatile',
|
|
1794
|
+
'deterministic',
|
|
1795
|
+
'region_code',
|
|
1796
|
+
'country_name',
|
|
1797
|
+
'city',
|
|
1798
|
+
'staging_area',
|
|
1799
|
+
'qos_class',
|
|
1800
|
+
'continent',
|
|
1801
|
+
'availability'
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
|
|
1805
|
+
@transactional_session
|
|
1806
|
+
def update_rse(rse_id: str, parameters: dict[str, Any], *, session: "Session"):
|
|
1807
|
+
"""
|
|
1808
|
+
Update RSE properties like availability or name.
|
|
1809
|
+
|
|
1810
|
+
:param rse_id: the id of the new rse.
|
|
1811
|
+
:param parameters: A dictionary with property (name, read, write, delete as keys).
|
|
1812
|
+
:param session: The database session in use.
|
|
1813
|
+
|
|
1814
|
+
:raises RSENotFound: If RSE is not found.
|
|
1815
|
+
:raises InputValidationError: If a parameter does not exist. Nothing will be added then.
|
|
1816
|
+
"""
|
|
1817
|
+
for key in parameters.keys():
|
|
1818
|
+
if key not in MUTABLE_RSE_PROPERTIES:
|
|
1819
|
+
raise exception.InputValidationError(f"The key '{key}' does not exist for RSE properties.")
|
|
1820
|
+
|
|
1821
|
+
try:
|
|
1822
|
+
stmt = select(
|
|
1823
|
+
models.RSE
|
|
1824
|
+
).where(
|
|
1825
|
+
models.RSE.id == rse_id
|
|
1826
|
+
)
|
|
1827
|
+
db_rse = session.execute(stmt).scalar_one()
|
|
1828
|
+
except sqlalchemy.orm.exc.NoResultFound:
|
|
1829
|
+
raise exception.RSENotFound('RSE with ID \'%s\' cannot be found' % rse_id)
|
|
1830
|
+
old_rse_name = db_rse.rse
|
|
1831
|
+
|
|
1832
|
+
param = {}
|
|
1833
|
+
|
|
1834
|
+
if 'availability' in parameters:
|
|
1835
|
+
availability = Availability.from_integer(parameters['availability'])
|
|
1836
|
+
param['availability_read'] = availability.read
|
|
1837
|
+
param['availability_write'] = availability.write
|
|
1838
|
+
param['availability_delete'] = availability.delete
|
|
1839
|
+
|
|
1840
|
+
for key in parameters:
|
|
1841
|
+
if key == 'name' and parameters['name'] != old_rse_name: # Needed due to wrongly setting name in pre1.22.7 clients
|
|
1842
|
+
param['rse'] = parameters['name']
|
|
1843
|
+
elif key in MUTABLE_RSE_PROPERTIES - {'name'}:
|
|
1844
|
+
param[key] = parameters[key]
|
|
1845
|
+
|
|
1846
|
+
# handle null-able keys
|
|
1847
|
+
for key in parameters:
|
|
1848
|
+
if key in ['qos_class']:
|
|
1849
|
+
if param[key] and param[key].lower() in ['', 'none', 'null']:
|
|
1850
|
+
param[key] = None
|
|
1851
|
+
|
|
1852
|
+
# handle rse settings
|
|
1853
|
+
for setting in set(param.keys()).intersection(RSE_SETTINGS):
|
|
1854
|
+
if has_rse_attribute(rse_id, setting, session=session):
|
|
1855
|
+
del_rse_attribute(rse_id, setting, session=session)
|
|
1856
|
+
add_rse_attribute(rse_id, setting, param[setting], session=session)
|
|
1857
|
+
|
|
1858
|
+
db_rse.update(param, session=session)
|
|
1859
|
+
if 'rse' in param:
|
|
1860
|
+
add_rse_attribute(rse_id=rse_id, key=parameters['name'], value=True, session=session)
|
|
1861
|
+
del_rse_attribute(rse_id=rse_id, key=old_rse_name, session=session)
|
|
1862
|
+
|
|
1863
|
+
|
|
1864
|
+
@read_session
|
|
1865
|
+
def export_rse(
|
|
1866
|
+
rse_id: str,
|
|
1867
|
+
*,
|
|
1868
|
+
session: "Session"
|
|
1869
|
+
) -> dict[str, Any]:
|
|
1870
|
+
"""
|
|
1871
|
+
Get the internal representation of an RSE.
|
|
1872
|
+
|
|
1873
|
+
:param rse_id: The RSE id.
|
|
1874
|
+
|
|
1875
|
+
:returns: A dictionary with the internal representation of an RSE.
|
|
1876
|
+
"""
|
|
1877
|
+
|
|
1878
|
+
stmt = select(
|
|
1879
|
+
models.RSE
|
|
1880
|
+
).where(
|
|
1881
|
+
models.RSE.id == rse_id
|
|
1882
|
+
)
|
|
1883
|
+
|
|
1884
|
+
rse_data = {}
|
|
1885
|
+
for _rse in session.execute(stmt).scalars():
|
|
1886
|
+
for k, v in _rse:
|
|
1887
|
+
rse_data[k] = v
|
|
1888
|
+
|
|
1889
|
+
rse_data.pop('continent')
|
|
1890
|
+
rse_data.pop('ASN')
|
|
1891
|
+
rse_data.pop('ISP')
|
|
1892
|
+
rse_data.pop('deleted')
|
|
1893
|
+
rse_data.pop('deleted_at')
|
|
1894
|
+
|
|
1895
|
+
# get RSE attributes
|
|
1896
|
+
rse_data['attributes'] = list_rse_attributes(rse_id=rse_id, session=session)
|
|
1897
|
+
|
|
1898
|
+
protocols = get_rse_protocols(rse_id=rse_id, session=session)
|
|
1899
|
+
rse_data['lfn2pfn_algorithm'] = protocols.get('lfn2pfn_algorithm')
|
|
1900
|
+
rse_data['verify_checksum'] = protocols.get('verify_checksum')
|
|
1901
|
+
rse_data['credentials'] = protocols.get('credentials')
|
|
1902
|
+
rse_data['availability_delete'] = protocols.get('availability_delete')
|
|
1903
|
+
rse_data['availability_write'] = protocols.get('availability_write')
|
|
1904
|
+
rse_data['availability_read'] = protocols.get('availability_read')
|
|
1905
|
+
rse_data['protocols'] = protocols.get('protocols')
|
|
1906
|
+
|
|
1907
|
+
# get RSE limits
|
|
1908
|
+
limits = get_rse_limits(rse_id=rse_id, session=session)
|
|
1909
|
+
rse_data['MinFreeSpace'] = limits.get('MinFreeSpace')
|
|
1910
|
+
|
|
1911
|
+
return rse_data
|
|
1912
|
+
|
|
1913
|
+
|
|
1914
|
+
@transactional_session
|
|
1915
|
+
def add_qos_policy(
|
|
1916
|
+
rse_id: str,
|
|
1917
|
+
qos_policy: str,
|
|
1918
|
+
*,
|
|
1919
|
+
session: "Session"
|
|
1920
|
+
) -> bool:
|
|
1921
|
+
"""
|
|
1922
|
+
Add a QoS policy from an RSE.
|
|
1923
|
+
|
|
1924
|
+
:param rse_id: The id of the RSE.
|
|
1925
|
+
:param qos_policy: The QoS policy to add.
|
|
1926
|
+
:param session: The database session in use.
|
|
1927
|
+
|
|
1928
|
+
:raises Duplicate: If the QoS policy already exists.
|
|
1929
|
+
:returns: True if successful, except otherwise.
|
|
1930
|
+
"""
|
|
1931
|
+
|
|
1932
|
+
try:
|
|
1933
|
+
new_qos_policy = models.RSEQoSAssociation()
|
|
1934
|
+
new_qos_policy.update({'rse_id': rse_id,
|
|
1935
|
+
'qos_policy': qos_policy})
|
|
1936
|
+
new_qos_policy.save(session=session)
|
|
1937
|
+
except (IntegrityError, FlushError, OperationalError) as error:
|
|
1938
|
+
if ('UNIQUE constraint failed' in error.args[0]) or ('conflicts with persistent instance' in error.args[0]) \
|
|
1939
|
+
or match('.*IntegrityError.*ORA-00001: unique constraint.*RSE_PROTOCOLS_PK.*violated.*', error.args[0]) \
|
|
1940
|
+
or match('.*IntegrityError.*1062.*Duplicate entry.*for key.*', error.args[0]) \
|
|
1941
|
+
or match('.*IntegrityError.*duplicate key value violates unique constraint.*', error.args[0])\
|
|
1942
|
+
or match('.*UniqueViolation.*duplicate key value violates unique constraint.*', error.args[0])\
|
|
1943
|
+
or match('.*IntegrityError.*columns.*are not unique.*', error.args[0]):
|
|
1944
|
+
raise exception.Duplicate('QoS policy %s already exists!' % qos_policy)
|
|
1945
|
+
except DatabaseError as error:
|
|
1946
|
+
raise exception.RucioException(error.args)
|
|
1947
|
+
|
|
1948
|
+
return True
|
|
1949
|
+
|
|
1950
|
+
|
|
1951
|
+
@transactional_session
|
|
1952
|
+
def delete_qos_policy(
|
|
1953
|
+
rse_id: str,
|
|
1954
|
+
qos_policy: str,
|
|
1955
|
+
*,
|
|
1956
|
+
session: "Session"
|
|
1957
|
+
) -> bool:
|
|
1958
|
+
"""
|
|
1959
|
+
Delete a QoS policy from an RSE.
|
|
1960
|
+
|
|
1961
|
+
:param rse_id: The id of the RSE.
|
|
1962
|
+
:param qos_policy: The QoS policy to delete.
|
|
1963
|
+
:param session: The database session in use.
|
|
1964
|
+
|
|
1965
|
+
:returns: True if successful, silent failure if QoS policy does not exist.
|
|
1966
|
+
"""
|
|
1967
|
+
|
|
1968
|
+
try:
|
|
1969
|
+
stmt = delete(
|
|
1970
|
+
models.RSEQoSAssociation
|
|
1971
|
+
).where(
|
|
1972
|
+
and_(models.RSEQoSAssociation.rse_id == rse_id,
|
|
1973
|
+
models.RSEQoSAssociation.qos_policy == qos_policy)
|
|
1974
|
+
)
|
|
1975
|
+
session.execute(stmt)
|
|
1976
|
+
except DatabaseError as error:
|
|
1977
|
+
raise exception.RucioException(error.args)
|
|
1978
|
+
|
|
1979
|
+
return True
|
|
1980
|
+
|
|
1981
|
+
|
|
1982
|
+
@read_session
|
|
1983
|
+
def list_qos_policies(
|
|
1984
|
+
rse_id: str,
|
|
1985
|
+
*,
|
|
1986
|
+
session: "Session"
|
|
1987
|
+
) -> list[str]:
|
|
1988
|
+
"""
|
|
1989
|
+
List all QoS policies of an RSE.
|
|
1990
|
+
|
|
1991
|
+
:param rse_id: The id of the RSE.
|
|
1992
|
+
:param session: The database session in use.
|
|
1993
|
+
|
|
1994
|
+
:returns: List containing all QoS policies.
|
|
1995
|
+
"""
|
|
1996
|
+
|
|
1997
|
+
qos_policies = []
|
|
1998
|
+
try:
|
|
1999
|
+
stmt = select(
|
|
2000
|
+
models.RSEQoSAssociation.qos_policy
|
|
2001
|
+
).where(
|
|
2002
|
+
models.RSEQoSAssociation.rse_id == rse_id
|
|
2003
|
+
)
|
|
2004
|
+
for qos_policy in session.execute(stmt).scalars():
|
|
2005
|
+
qos_policies.append(qos_policy)
|
|
2006
|
+
except DatabaseError as error:
|
|
2007
|
+
raise exception.RucioException(error.args)
|
|
2008
|
+
|
|
2009
|
+
return qos_policies
|
|
2010
|
+
|
|
2011
|
+
|
|
2012
|
+
@transactional_session
|
|
2013
|
+
def fill_rse_expired(
|
|
2014
|
+
rse_id: str,
|
|
2015
|
+
*,
|
|
2016
|
+
session: "Session"
|
|
2017
|
+
) -> None:
|
|
2018
|
+
"""
|
|
2019
|
+
Fill the rse_usage for source expired
|
|
2020
|
+
|
|
2021
|
+
:param rse_id: The RSE id.
|
|
2022
|
+
"""
|
|
2023
|
+
stmt = select(
|
|
2024
|
+
func.sum(models.RSEFileAssociation.bytes).label("bytes"),
|
|
2025
|
+
func.count().label("length")
|
|
2026
|
+
).with_hint(
|
|
2027
|
+
models.RSEFileAssociation,
|
|
2028
|
+
'INDEX(REPLICAS REPLICAS_RSE_ID_TOMBSTONE_IDX)',
|
|
2029
|
+
'oracle'
|
|
2030
|
+
).where(
|
|
2031
|
+
and_(models.RSEFileAssociation.tombstone < datetime.utcnow(),
|
|
2032
|
+
models.RSEFileAssociation.lock_cnt == 0,
|
|
2033
|
+
models.RSEFileAssociation.rse_id == rse_id,
|
|
2034
|
+
models.RSEFileAssociation.state.in_((ReplicaState.AVAILABLE, ReplicaState.UNAVAILABLE, ReplicaState.BAD)))
|
|
2035
|
+
)
|
|
2036
|
+
|
|
2037
|
+
sum_bytes, sum_files = session.execute(stmt).one()
|
|
2038
|
+
models.RSEUsage(rse_id=rse_id,
|
|
2039
|
+
used=sum_bytes,
|
|
2040
|
+
files=sum_files,
|
|
2041
|
+
source='expired').save(session=session)
|
|
2042
|
+
|
|
2043
|
+
|
|
2044
|
+
def determine_audience_for_rse(rse_id: str) -> str:
|
|
2045
|
+
"""Construct the Audience claim for an RSE."""
|
|
2046
|
+
rse_protocols = get_rse_protocols(rse_id)
|
|
2047
|
+
# FIXME: At the time of writing, there does not appear to be a common
|
|
2048
|
+
# agreement on how sites will configure their storages. Rucio had requested
|
|
2049
|
+
# that the protocol hostname be sufficient, but this may not come to pass.
|
|
2050
|
+
filtered_hostnames = {p['hostname']
|
|
2051
|
+
for p in rse_protocols['protocols']
|
|
2052
|
+
if p['scheme'] == 'davs'}
|
|
2053
|
+
return ' '.join(sorted(filtered_hostnames))
|
|
2054
|
+
|
|
2055
|
+
|
|
2056
|
+
def determine_scope_for_rse(
|
|
2057
|
+
rse_id: str,
|
|
2058
|
+
scopes: 'Iterable[str]',
|
|
2059
|
+
extra_scopes: Optional['Iterable[str]'] = None,
|
|
2060
|
+
) -> str:
|
|
2061
|
+
"""Construct the Scope claim for an RSE."""
|
|
2062
|
+
if extra_scopes is None:
|
|
2063
|
+
extra_scopes = []
|
|
2064
|
+
rse_protocols = get_rse_protocols(rse_id)
|
|
2065
|
+
filtered_prefixes = set()
|
|
2066
|
+
for protocol in rse_protocols['protocols']:
|
|
2067
|
+
# Token support is exclusive to WebDAV.
|
|
2068
|
+
if protocol['scheme'] != 'davs':
|
|
2069
|
+
continue
|
|
2070
|
+
# Remove base path from prefix. Storages typically map an issuer (i.e.
|
|
2071
|
+
# a VO) to a particular area. If so, then the path to that area acts as
|
|
2072
|
+
# a base which should be removed from the prefix (in order for '/' to
|
|
2073
|
+
# mean the entire resource associated with that issuer).
|
|
2074
|
+
prefix = protocol['prefix']
|
|
2075
|
+
if base_path := get_rse_attribute(rse_id, RseAttr.OIDC_BASE_PATH): # type: ignore (session parameter missing)
|
|
2076
|
+
prefix = prefix.removeprefix(base_path)
|
|
2077
|
+
filtered_prefixes.add(prefix)
|
|
2078
|
+
all_scopes = [f'{s}:{p}' for s in scopes for p in filtered_prefixes] + list(extra_scopes)
|
|
2079
|
+
return ' '.join(sorted(all_scopes))
|