rucio 32.8.6__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 +18 -0
- rucio/alembicrevision.py +16 -0
- rucio/api/__init__.py +14 -0
- rucio/api/account.py +266 -0
- rucio/api/account_limit.py +287 -0
- rucio/api/authentication.py +302 -0
- rucio/api/config.py +218 -0
- rucio/api/credential.py +60 -0
- rucio/api/did.py +726 -0
- rucio/api/dirac.py +71 -0
- rucio/api/exporter.py +60 -0
- rucio/api/heartbeat.py +62 -0
- rucio/api/identity.py +160 -0
- rucio/api/importer.py +46 -0
- rucio/api/lifetime_exception.py +95 -0
- rucio/api/lock.py +131 -0
- rucio/api/meta.py +85 -0
- rucio/api/permission.py +72 -0
- rucio/api/quarantined_replica.py +69 -0
- rucio/api/replica.py +528 -0
- rucio/api/request.py +220 -0
- rucio/api/rse.py +601 -0
- rucio/api/rule.py +335 -0
- rucio/api/scope.py +89 -0
- rucio/api/subscription.py +255 -0
- rucio/api/temporary_did.py +49 -0
- rucio/api/vo.py +112 -0
- rucio/client/__init__.py +16 -0
- rucio/client/accountclient.py +413 -0
- rucio/client/accountlimitclient.py +155 -0
- rucio/client/baseclient.py +929 -0
- rucio/client/client.py +77 -0
- rucio/client/configclient.py +113 -0
- rucio/client/credentialclient.py +54 -0
- rucio/client/didclient.py +691 -0
- rucio/client/diracclient.py +48 -0
- rucio/client/downloadclient.py +1674 -0
- rucio/client/exportclient.py +44 -0
- rucio/client/fileclient.py +51 -0
- rucio/client/importclient.py +42 -0
- rucio/client/lifetimeclient.py +74 -0
- rucio/client/lockclient.py +99 -0
- rucio/client/metaclient.py +137 -0
- rucio/client/pingclient.py +45 -0
- rucio/client/replicaclient.py +444 -0
- rucio/client/requestclient.py +109 -0
- rucio/client/rseclient.py +664 -0
- rucio/client/ruleclient.py +287 -0
- rucio/client/scopeclient.py +88 -0
- rucio/client/subscriptionclient.py +161 -0
- rucio/client/touchclient.py +78 -0
- rucio/client/uploadclient.py +871 -0
- rucio/common/__init__.py +14 -0
- rucio/common/cache.py +74 -0
- rucio/common/config.py +796 -0
- rucio/common/constants.py +92 -0
- rucio/common/constraints.py +18 -0
- rucio/common/didtype.py +187 -0
- rucio/common/dumper/__init__.py +306 -0
- rucio/common/dumper/consistency.py +449 -0
- rucio/common/dumper/data_models.py +325 -0
- rucio/common/dumper/path_parsing.py +65 -0
- rucio/common/exception.py +1092 -0
- rucio/common/extra.py +37 -0
- rucio/common/logging.py +404 -0
- rucio/common/pcache.py +1387 -0
- rucio/common/policy.py +84 -0
- rucio/common/schema/__init__.py +143 -0
- rucio/common/schema/atlas.py +411 -0
- rucio/common/schema/belleii.py +406 -0
- rucio/common/schema/cms.py +478 -0
- rucio/common/schema/domatpc.py +399 -0
- rucio/common/schema/escape.py +424 -0
- rucio/common/schema/generic.py +431 -0
- rucio/common/schema/generic_multi_vo.py +410 -0
- rucio/common/schema/icecube.py +404 -0
- rucio/common/schema/lsst.py +423 -0
- rucio/common/stomp_utils.py +160 -0
- rucio/common/stopwatch.py +56 -0
- rucio/common/test_rucio_server.py +148 -0
- rucio/common/types.py +158 -0
- rucio/common/utils.py +1946 -0
- rucio/core/__init__.py +14 -0
- rucio/core/account.py +426 -0
- rucio/core/account_counter.py +171 -0
- rucio/core/account_limit.py +357 -0
- rucio/core/authentication.py +563 -0
- rucio/core/config.py +386 -0
- rucio/core/credential.py +218 -0
- rucio/core/did.py +3102 -0
- rucio/core/did_meta_plugins/__init__.py +250 -0
- rucio/core/did_meta_plugins/did_column_meta.py +326 -0
- rucio/core/did_meta_plugins/did_meta_plugin_interface.py +116 -0
- rucio/core/did_meta_plugins/filter_engine.py +573 -0
- rucio/core/did_meta_plugins/json_meta.py +215 -0
- rucio/core/did_meta_plugins/mongo_meta.py +199 -0
- rucio/core/did_meta_plugins/postgres_meta.py +317 -0
- rucio/core/dirac.py +208 -0
- rucio/core/distance.py +164 -0
- rucio/core/exporter.py +59 -0
- rucio/core/heartbeat.py +263 -0
- rucio/core/identity.py +290 -0
- rucio/core/importer.py +248 -0
- rucio/core/lifetime_exception.py +377 -0
- rucio/core/lock.py +474 -0
- rucio/core/message.py +241 -0
- rucio/core/meta.py +190 -0
- rucio/core/monitor.py +441 -0
- rucio/core/naming_convention.py +154 -0
- rucio/core/nongrid_trace.py +124 -0
- rucio/core/oidc.py +1339 -0
- rucio/core/permission/__init__.py +107 -0
- rucio/core/permission/atlas.py +1333 -0
- rucio/core/permission/belleii.py +1076 -0
- rucio/core/permission/cms.py +1166 -0
- rucio/core/permission/escape.py +1076 -0
- rucio/core/permission/generic.py +1128 -0
- rucio/core/permission/generic_multi_vo.py +1148 -0
- rucio/core/quarantined_replica.py +190 -0
- rucio/core/replica.py +3627 -0
- rucio/core/replica_sorter.py +368 -0
- rucio/core/request.py +2241 -0
- rucio/core/rse.py +1835 -0
- rucio/core/rse_counter.py +155 -0
- rucio/core/rse_expression_parser.py +460 -0
- rucio/core/rse_selector.py +277 -0
- rucio/core/rule.py +3419 -0
- rucio/core/rule_grouping.py +1473 -0
- rucio/core/scope.py +152 -0
- rucio/core/subscription.py +316 -0
- rucio/core/temporary_did.py +188 -0
- rucio/core/topology.py +448 -0
- rucio/core/trace.py +361 -0
- rucio/core/transfer.py +1233 -0
- rucio/core/vo.py +151 -0
- rucio/core/volatile_replica.py +123 -0
- rucio/daemons/__init__.py +14 -0
- rucio/daemons/abacus/__init__.py +14 -0
- rucio/daemons/abacus/account.py +106 -0
- rucio/daemons/abacus/collection_replica.py +113 -0
- rucio/daemons/abacus/rse.py +107 -0
- rucio/daemons/atropos/__init__.py +14 -0
- rucio/daemons/atropos/atropos.py +243 -0
- rucio/daemons/auditor/__init__.py +261 -0
- rucio/daemons/auditor/hdfs.py +86 -0
- rucio/daemons/auditor/srmdumps.py +284 -0
- rucio/daemons/automatix/__init__.py +14 -0
- rucio/daemons/automatix/automatix.py +281 -0
- rucio/daemons/badreplicas/__init__.py +14 -0
- rucio/daemons/badreplicas/minos.py +311 -0
- rucio/daemons/badreplicas/minos_temporary_expiration.py +173 -0
- rucio/daemons/badreplicas/necromancer.py +200 -0
- rucio/daemons/bb8/__init__.py +14 -0
- rucio/daemons/bb8/bb8.py +356 -0
- rucio/daemons/bb8/common.py +762 -0
- rucio/daemons/bb8/nuclei_background_rebalance.py +147 -0
- rucio/daemons/bb8/t2_background_rebalance.py +146 -0
- rucio/daemons/c3po/__init__.py +14 -0
- rucio/daemons/c3po/algorithms/__init__.py +14 -0
- rucio/daemons/c3po/algorithms/simple.py +131 -0
- rucio/daemons/c3po/algorithms/t2_free_space.py +125 -0
- rucio/daemons/c3po/algorithms/t2_free_space_only_pop.py +127 -0
- rucio/daemons/c3po/algorithms/t2_free_space_only_pop_with_network.py +279 -0
- rucio/daemons/c3po/c3po.py +342 -0
- rucio/daemons/c3po/collectors/__init__.py +14 -0
- rucio/daemons/c3po/collectors/agis.py +108 -0
- rucio/daemons/c3po/collectors/free_space.py +62 -0
- rucio/daemons/c3po/collectors/jedi_did.py +48 -0
- rucio/daemons/c3po/collectors/mock_did.py +46 -0
- rucio/daemons/c3po/collectors/network_metrics.py +63 -0
- rucio/daemons/c3po/collectors/workload.py +110 -0
- rucio/daemons/c3po/utils/__init__.py +14 -0
- rucio/daemons/c3po/utils/dataset_cache.py +40 -0
- rucio/daemons/c3po/utils/expiring_dataset_cache.py +45 -0
- rucio/daemons/c3po/utils/expiring_list.py +63 -0
- rucio/daemons/c3po/utils/popularity.py +82 -0
- rucio/daemons/c3po/utils/timeseries.py +76 -0
- rucio/daemons/cache/__init__.py +14 -0
- rucio/daemons/cache/consumer.py +191 -0
- rucio/daemons/common.py +391 -0
- rucio/daemons/conveyor/__init__.py +14 -0
- rucio/daemons/conveyor/common.py +530 -0
- rucio/daemons/conveyor/finisher.py +492 -0
- rucio/daemons/conveyor/poller.py +372 -0
- rucio/daemons/conveyor/preparer.py +198 -0
- rucio/daemons/conveyor/receiver.py +206 -0
- rucio/daemons/conveyor/stager.py +127 -0
- rucio/daemons/conveyor/submitter.py +379 -0
- rucio/daemons/conveyor/throttler.py +468 -0
- rucio/daemons/follower/__init__.py +14 -0
- rucio/daemons/follower/follower.py +97 -0
- rucio/daemons/hermes/__init__.py +14 -0
- rucio/daemons/hermes/hermes.py +738 -0
- rucio/daemons/judge/__init__.py +14 -0
- rucio/daemons/judge/cleaner.py +149 -0
- rucio/daemons/judge/evaluator.py +172 -0
- rucio/daemons/judge/injector.py +154 -0
- rucio/daemons/judge/repairer.py +144 -0
- rucio/daemons/oauthmanager/__init__.py +14 -0
- rucio/daemons/oauthmanager/oauthmanager.py +199 -0
- rucio/daemons/reaper/__init__.py +14 -0
- rucio/daemons/reaper/dark_reaper.py +272 -0
- rucio/daemons/reaper/light_reaper.py +255 -0
- rucio/daemons/reaper/reaper.py +701 -0
- rucio/daemons/replicarecoverer/__init__.py +14 -0
- rucio/daemons/replicarecoverer/suspicious_replica_recoverer.py +487 -0
- rucio/daemons/storage/__init__.py +14 -0
- rucio/daemons/storage/consistency/__init__.py +14 -0
- rucio/daemons/storage/consistency/actions.py +753 -0
- rucio/daemons/tracer/__init__.py +14 -0
- rucio/daemons/tracer/kronos.py +513 -0
- rucio/daemons/transmogrifier/__init__.py +14 -0
- rucio/daemons/transmogrifier/transmogrifier.py +753 -0
- rucio/daemons/undertaker/__init__.py +14 -0
- rucio/daemons/undertaker/undertaker.py +137 -0
- rucio/db/__init__.py +14 -0
- rucio/db/sqla/__init__.py +38 -0
- rucio/db/sqla/constants.py +192 -0
- rucio/db/sqla/migrate_repo/__init__.py +14 -0
- rucio/db/sqla/migrate_repo/env.py +111 -0
- rucio/db/sqla/migrate_repo/versions/01eaf73ab656_add_new_rule_notification_state_progress.py +71 -0
- rucio/db/sqla/migrate_repo/versions/0437a40dbfd1_add_eol_at_in_rules.py +50 -0
- rucio/db/sqla/migrate_repo/versions/0f1adb7a599a_create_transfer_hops_table.py +61 -0
- rucio/db/sqla/migrate_repo/versions/102efcf145f4_added_stuck_at_column_to_rules.py +46 -0
- rucio/db/sqla/migrate_repo/versions/13d4f70c66a9_introduce_transfer_limits.py +93 -0
- rucio/db/sqla/migrate_repo/versions/140fef722e91_cleanup_distances_table.py +78 -0
- rucio/db/sqla/migrate_repo/versions/14ec5aeb64cf_add_request_external_host.py +46 -0
- rucio/db/sqla/migrate_repo/versions/156fb5b5a14_add_request_type_to_requests_idx.py +53 -0
- rucio/db/sqla/migrate_repo/versions/1677d4d803c8_split_rse_availability_into_multiple.py +69 -0
- rucio/db/sqla/migrate_repo/versions/16a0aca82e12_create_index_on_table_replicas_path.py +42 -0
- rucio/db/sqla/migrate_repo/versions/1803333ac20f_adding_provenance_and_phys_group.py +46 -0
- rucio/db/sqla/migrate_repo/versions/1a29d6a9504c_add_didtype_chck_to_requests.py +61 -0
- rucio/db/sqla/migrate_repo/versions/1a80adff031a_create_index_on_rules_hist_recent.py +42 -0
- rucio/db/sqla/migrate_repo/versions/1c45d9730ca6_increase_identity_length.py +141 -0
- rucio/db/sqla/migrate_repo/versions/1d1215494e95_add_quarantined_replicas_table.py +75 -0
- rucio/db/sqla/migrate_repo/versions/1d96f484df21_asynchronous_rules_and_rule_approval.py +75 -0
- rucio/db/sqla/migrate_repo/versions/1f46c5f240ac_add_bytes_column_to_bad_replicas.py +46 -0
- rucio/db/sqla/migrate_repo/versions/1fc15ab60d43_add_message_history_table.py +51 -0
- rucio/db/sqla/migrate_repo/versions/2190e703eb6e_move_rse_settings_to_rse_attributes.py +135 -0
- rucio/db/sqla/migrate_repo/versions/21d6b9dc9961_add_mismatch_scheme_state_to_requests.py +65 -0
- rucio/db/sqla/migrate_repo/versions/22cf51430c78_add_availability_column_to_table_rses.py +42 -0
- rucio/db/sqla/migrate_repo/versions/22d887e4ec0a_create_sources_table.py +66 -0
- rucio/db/sqla/migrate_repo/versions/25821a8a45a3_remove_unique_constraint_on_requests.py +54 -0
- rucio/db/sqla/migrate_repo/versions/25fc855625cf_added_unique_constraint_to_rules.py +43 -0
- rucio/db/sqla/migrate_repo/versions/269fee20dee9_add_repair_cnt_to_locks.py +46 -0
- rucio/db/sqla/migrate_repo/versions/271a46ea6244_add_ignore_availability_column_to_rules.py +47 -0
- rucio/db/sqla/migrate_repo/versions/277b5fbb41d3_switch_heartbeats_executable.py +54 -0
- rucio/db/sqla/migrate_repo/versions/27e3a68927fb_remove_replicas_tombstone_and_replicas_.py +39 -0
- rucio/db/sqla/migrate_repo/versions/2854cd9e168_added_rule_id_column.py +48 -0
- rucio/db/sqla/migrate_repo/versions/295289b5a800_processed_by_and__at_in_requests.py +47 -0
- rucio/db/sqla/migrate_repo/versions/2962ece31cf4_add_nbaccesses_column_in_the_did_table.py +48 -0
- rucio/db/sqla/migrate_repo/versions/2af3291ec4c_added_replicas_history_table.py +59 -0
- rucio/db/sqla/migrate_repo/versions/2b69addda658_add_columns_for_third_party_copy_read_.py +47 -0
- rucio/db/sqla/migrate_repo/versions/2b8e7bcb4783_add_config_table.py +72 -0
- rucio/db/sqla/migrate_repo/versions/2ba5229cb54c_add_submitted_at_to_requests_table.py +46 -0
- rucio/db/sqla/migrate_repo/versions/2cbee484dcf9_added_column_volume_to_rse_transfer_.py +45 -0
- rucio/db/sqla/migrate_repo/versions/2edee4a83846_add_source_to_requests_and_requests_.py +48 -0
- rucio/db/sqla/migrate_repo/versions/2eef46be23d4_change_tokens_pk.py +48 -0
- rucio/db/sqla/migrate_repo/versions/2f648fc909f3_index_in_rule_history_on_scope_name.py +42 -0
- rucio/db/sqla/migrate_repo/versions/3082b8cef557_add_naming_convention_table_and_closed_.py +69 -0
- rucio/db/sqla/migrate_repo/versions/30fa38b6434e_add_index_on_service_column_in_the_message_table.py +46 -0
- rucio/db/sqla/migrate_repo/versions/3152492b110b_added_staging_area_column.py +78 -0
- rucio/db/sqla/migrate_repo/versions/32c7d2783f7e_create_bad_replicas_table.py +62 -0
- rucio/db/sqla/migrate_repo/versions/3345511706b8_replicas_table_pk_definition_is_in_.py +74 -0
- rucio/db/sqla/migrate_repo/versions/35ef10d1e11b_change_index_on_table_requests.py +44 -0
- rucio/db/sqla/migrate_repo/versions/379a19b5332d_create_rse_limits_table.py +67 -0
- rucio/db/sqla/migrate_repo/versions/384b96aa0f60_created_rule_history_tables.py +134 -0
- rucio/db/sqla/migrate_repo/versions/3ac1660a1a72_extend_distance_table.py +58 -0
- rucio/db/sqla/migrate_repo/versions/3ad36e2268b0_create_collection_replicas_updates_table.py +79 -0
- rucio/db/sqla/migrate_repo/versions/3c9df354071b_extend_waiting_request_state.py +61 -0
- rucio/db/sqla/migrate_repo/versions/3d9813fab443_add_a_new_state_lost_in_badfilesstatus.py +45 -0
- rucio/db/sqla/migrate_repo/versions/40ad39ce3160_add_transferred_at_to_requests_table.py +46 -0
- rucio/db/sqla/migrate_repo/versions/4207be2fd914_add_notification_column_to_rules.py +65 -0
- rucio/db/sqla/migrate_repo/versions/42db2617c364_create_index_on_requests_external_id.py +42 -0
- rucio/db/sqla/migrate_repo/versions/436827b13f82_added_column_activity_to_table_requests.py +46 -0
- rucio/db/sqla/migrate_repo/versions/44278720f774_update_requests_typ_sta_upd_idx_index.py +46 -0
- rucio/db/sqla/migrate_repo/versions/45378a1e76a8_create_collection_replica_table.py +80 -0
- rucio/db/sqla/migrate_repo/versions/469d262be19_removing_created_at_index.py +43 -0
- rucio/db/sqla/migrate_repo/versions/4783c1f49cb4_create_distance_table.py +61 -0
- rucio/db/sqla/migrate_repo/versions/49a21b4d4357_create_index_on_table_tokens.py +47 -0
- rucio/db/sqla/migrate_repo/versions/4a2cbedda8b9_add_source_replica_expression_column_to_.py +46 -0
- rucio/db/sqla/migrate_repo/versions/4a7182d9578b_added_bytes_length_accessed_at_columns.py +52 -0
- rucio/db/sqla/migrate_repo/versions/4bab9edd01fc_create_index_on_requests_rule_id.py +42 -0
- rucio/db/sqla/migrate_repo/versions/4c3a4acfe006_new_attr_account_table.py +65 -0
- rucio/db/sqla/migrate_repo/versions/4cf0a2e127d4_adding_transient_metadata.py +46 -0
- rucio/db/sqla/migrate_repo/versions/50280c53117c_add_qos_class_to_rse.py +47 -0
- rucio/db/sqla/migrate_repo/versions/52153819589c_add_rse_id_to_replicas_table.py +45 -0
- rucio/db/sqla/migrate_repo/versions/52fd9f4916fa_added_activity_to_rules.py +46 -0
- rucio/db/sqla/migrate_repo/versions/53b479c3cb0f_fix_did_meta_table_missing_updated_at_.py +48 -0
- rucio/db/sqla/migrate_repo/versions/5673b4b6e843_add_wfms_metadata_to_rule_tables.py +50 -0
- rucio/db/sqla/migrate_repo/versions/575767d9f89_added_source_history_table.py +59 -0
- rucio/db/sqla/migrate_repo/versions/58bff7008037_add_started_at_to_requests.py +48 -0
- rucio/db/sqla/migrate_repo/versions/58c8b78301ab_rename_callback_to_message.py +108 -0
- rucio/db/sqla/migrate_repo/versions/5f139f77382a_added_child_rule_id_column.py +57 -0
- rucio/db/sqla/migrate_repo/versions/688ef1840840_adding_did_meta_table.py +51 -0
- rucio/db/sqla/migrate_repo/versions/6e572a9bfbf3_add_new_split_container_column_to_rules.py +50 -0
- rucio/db/sqla/migrate_repo/versions/70587619328_add_comment_column_for_subscriptions.py +46 -0
- rucio/db/sqla/migrate_repo/versions/739064d31565_remove_history_table_pks.py +42 -0
- rucio/db/sqla/migrate_repo/versions/7541902bf173_add_didsfollowed_and_followevents_table.py +93 -0
- rucio/db/sqla/migrate_repo/versions/7ec22226cdbf_new_replica_state_for_temporary_.py +73 -0
- rucio/db/sqla/migrate_repo/versions/810a41685bc1_added_columns_rse_transfer_limits.py +52 -0
- rucio/db/sqla/migrate_repo/versions/83f991c63a93_correct_rse_expression_length.py +45 -0
- rucio/db/sqla/migrate_repo/versions/8523998e2e76_increase_size_of_extended_attributes_.py +46 -0
- rucio/db/sqla/migrate_repo/versions/8ea9122275b1_adding_missing_function_based_indices.py +54 -0
- rucio/db/sqla/migrate_repo/versions/90f47792bb76_add_clob_payload_to_messages.py +48 -0
- rucio/db/sqla/migrate_repo/versions/914b8f02df38_new_table_for_lifetime_model_exceptions.py +70 -0
- rucio/db/sqla/migrate_repo/versions/94a5961ddbf2_add_estimator_columns.py +48 -0
- rucio/db/sqla/migrate_repo/versions/9a1b149a2044_add_saml_identity_type.py +95 -0
- rucio/db/sqla/migrate_repo/versions/9a45bc4ea66d_add_vp_table.py +55 -0
- rucio/db/sqla/migrate_repo/versions/9eb936a81eb1_true_is_true.py +74 -0
- rucio/db/sqla/migrate_repo/versions/a118956323f8_added_vo_table_and_vo_col_to_rse.py +78 -0
- rucio/db/sqla/migrate_repo/versions/a193a275255c_add_status_column_in_messages.py +49 -0
- rucio/db/sqla/migrate_repo/versions/a5f6f6e928a7_1_7_0.py +124 -0
- rucio/db/sqla/migrate_repo/versions/a616581ee47_added_columns_to_table_requests.py +60 -0
- rucio/db/sqla/migrate_repo/versions/a6eb23955c28_state_idx_non_functional.py +53 -0
- rucio/db/sqla/migrate_repo/versions/a74275a1ad30_added_global_quota_table.py +56 -0
- rucio/db/sqla/migrate_repo/versions/a93e4e47bda_heartbeats.py +67 -0
- rucio/db/sqla/migrate_repo/versions/ae2a56fcc89_added_comment_column_to_rules.py +50 -0
- rucio/db/sqla/migrate_repo/versions/b4293a99f344_added_column_identity_to_table_tokens.py +46 -0
- rucio/db/sqla/migrate_repo/versions/b7d287de34fd_removal_of_replicastate_source.py +92 -0
- rucio/db/sqla/migrate_repo/versions/b818052fa670_add_index_to_quarantined_replicas.py +42 -0
- rucio/db/sqla/migrate_repo/versions/b8caac94d7f0_add_comments_column_for_subscriptions_.py +46 -0
- rucio/db/sqla/migrate_repo/versions/b96a1c7e1cc4_new_bad_pfns_table_and_bad_replicas_.py +147 -0
- rucio/db/sqla/migrate_repo/versions/bb695f45c04_extend_request_state.py +78 -0
- rucio/db/sqla/migrate_repo/versions/bc68e9946deb_add_staging_timestamps_to_request.py +53 -0
- rucio/db/sqla/migrate_repo/versions/bf3baa1c1474_correct_pk_and_idx_for_history_tables.py +74 -0
- rucio/db/sqla/migrate_repo/versions/c0937668555f_add_qos_policy_map_table.py +56 -0
- rucio/db/sqla/migrate_repo/versions/c129ccdb2d5_add_lumiblocknr_to_dids.py +46 -0
- rucio/db/sqla/migrate_repo/versions/ccdbcd48206e_add_did_type_column_index_on_did_meta_.py +68 -0
- rucio/db/sqla/migrate_repo/versions/cebad904c4dd_new_payload_column_for_heartbeats.py +48 -0
- rucio/db/sqla/migrate_repo/versions/d1189a09c6e0_oauth2_0_and_jwt_feature_support_adding_.py +149 -0
- rucio/db/sqla/migrate_repo/versions/d23453595260_extend_request_state_for_preparer.py +106 -0
- rucio/db/sqla/migrate_repo/versions/d6dceb1de2d_added_purge_column_to_rules.py +47 -0
- rucio/db/sqla/migrate_repo/versions/d6e2c3b2cf26_remove_third_party_copy_column_from_rse.py +45 -0
- rucio/db/sqla/migrate_repo/versions/d91002c5841_new_account_limits_table.py +105 -0
- rucio/db/sqla/migrate_repo/versions/e138c364ebd0_extending_columns_for_filter_and_.py +52 -0
- rucio/db/sqla/migrate_repo/versions/e59300c8b179_support_for_archive.py +106 -0
- rucio/db/sqla/migrate_repo/versions/f1b14a8c2ac1_postgres_use_check_constraints.py +30 -0
- rucio/db/sqla/migrate_repo/versions/f41ffe206f37_oracle_global_temporary_tables.py +75 -0
- rucio/db/sqla/migrate_repo/versions/f85a2962b021_adding_transfertool_column_to_requests_.py +49 -0
- rucio/db/sqla/migrate_repo/versions/fa7a7d78b602_increase_refresh_token_size.py +45 -0
- rucio/db/sqla/migrate_repo/versions/fb28a95fe288_add_replicas_rse_id_tombstone_idx.py +38 -0
- rucio/db/sqla/migrate_repo/versions/fe1a65b176c9_set_third_party_copy_read_and_write_.py +44 -0
- rucio/db/sqla/migrate_repo/versions/fe8ea2fa9788_added_third_party_copy_column_to_rse_.py +46 -0
- rucio/db/sqla/models.py +1834 -0
- rucio/db/sqla/sautils.py +48 -0
- rucio/db/sqla/session.py +470 -0
- rucio/db/sqla/types.py +207 -0
- rucio/db/sqla/util.py +521 -0
- rucio/rse/__init__.py +97 -0
- rucio/rse/protocols/__init__.py +14 -0
- rucio/rse/protocols/cache.py +123 -0
- rucio/rse/protocols/dummy.py +112 -0
- rucio/rse/protocols/gfal.py +701 -0
- rucio/rse/protocols/globus.py +243 -0
- rucio/rse/protocols/gsiftp.py +93 -0
- rucio/rse/protocols/http_cache.py +83 -0
- rucio/rse/protocols/mock.py +124 -0
- rucio/rse/protocols/ngarc.py +210 -0
- rucio/rse/protocols/posix.py +251 -0
- rucio/rse/protocols/protocol.py +530 -0
- rucio/rse/protocols/rclone.py +365 -0
- rucio/rse/protocols/rfio.py +137 -0
- rucio/rse/protocols/srm.py +339 -0
- rucio/rse/protocols/ssh.py +414 -0
- rucio/rse/protocols/storm.py +207 -0
- rucio/rse/protocols/webdav.py +547 -0
- rucio/rse/protocols/xrootd.py +295 -0
- rucio/rse/rsemanager.py +752 -0
- rucio/tests/__init__.py +14 -0
- rucio/tests/common.py +244 -0
- rucio/tests/common_server.py +132 -0
- rucio/transfertool/__init__.py +14 -0
- rucio/transfertool/fts3.py +1484 -0
- rucio/transfertool/globus.py +200 -0
- rucio/transfertool/globus_library.py +182 -0
- rucio/transfertool/mock.py +81 -0
- rucio/transfertool/transfertool.py +212 -0
- rucio/vcsversion.py +11 -0
- rucio/version.py +46 -0
- rucio/web/__init__.py +14 -0
- rucio/web/rest/__init__.py +14 -0
- rucio/web/rest/flaskapi/__init__.py +14 -0
- rucio/web/rest/flaskapi/authenticated_bp.py +28 -0
- rucio/web/rest/flaskapi/v1/__init__.py +14 -0
- rucio/web/rest/flaskapi/v1/accountlimits.py +234 -0
- rucio/web/rest/flaskapi/v1/accounts.py +1088 -0
- rucio/web/rest/flaskapi/v1/archives.py +100 -0
- rucio/web/rest/flaskapi/v1/auth.py +1642 -0
- rucio/web/rest/flaskapi/v1/common.py +385 -0
- rucio/web/rest/flaskapi/v1/config.py +305 -0
- rucio/web/rest/flaskapi/v1/credentials.py +213 -0
- rucio/web/rest/flaskapi/v1/dids.py +2204 -0
- rucio/web/rest/flaskapi/v1/dirac.py +116 -0
- rucio/web/rest/flaskapi/v1/export.py +77 -0
- rucio/web/rest/flaskapi/v1/heartbeats.py +129 -0
- rucio/web/rest/flaskapi/v1/identities.py +263 -0
- rucio/web/rest/flaskapi/v1/import.py +133 -0
- rucio/web/rest/flaskapi/v1/lifetime_exceptions.py +315 -0
- rucio/web/rest/flaskapi/v1/locks.py +360 -0
- rucio/web/rest/flaskapi/v1/main.py +83 -0
- rucio/web/rest/flaskapi/v1/meta.py +226 -0
- rucio/web/rest/flaskapi/v1/metrics.py +37 -0
- rucio/web/rest/flaskapi/v1/nongrid_traces.py +97 -0
- rucio/web/rest/flaskapi/v1/ping.py +89 -0
- rucio/web/rest/flaskapi/v1/redirect.py +366 -0
- rucio/web/rest/flaskapi/v1/replicas.py +1866 -0
- rucio/web/rest/flaskapi/v1/requests.py +841 -0
- rucio/web/rest/flaskapi/v1/rses.py +2204 -0
- rucio/web/rest/flaskapi/v1/rules.py +824 -0
- rucio/web/rest/flaskapi/v1/scopes.py +161 -0
- rucio/web/rest/flaskapi/v1/subscriptions.py +646 -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/tmp_dids.py +115 -0
- rucio/web/rest/flaskapi/v1/traces.py +100 -0
- rucio/web/rest/flaskapi/v1/vos.py +280 -0
- rucio/web/rest/main.py +19 -0
- rucio/web/rest/metrics.py +28 -0
- rucio-32.8.6.data/data/rucio/etc/alembic.ini.template +71 -0
- rucio-32.8.6.data/data/rucio/etc/alembic_offline.ini.template +74 -0
- rucio-32.8.6.data/data/rucio/etc/globus-config.yml.template +5 -0
- rucio-32.8.6.data/data/rucio/etc/ldap.cfg.template +30 -0
- rucio-32.8.6.data/data/rucio/etc/mail_templates/rule_approval_request.tmpl +38 -0
- rucio-32.8.6.data/data/rucio/etc/mail_templates/rule_approved_admin.tmpl +4 -0
- rucio-32.8.6.data/data/rucio/etc/mail_templates/rule_approved_user.tmpl +17 -0
- rucio-32.8.6.data/data/rucio/etc/mail_templates/rule_denied_admin.tmpl +6 -0
- rucio-32.8.6.data/data/rucio/etc/mail_templates/rule_denied_user.tmpl +17 -0
- rucio-32.8.6.data/data/rucio/etc/mail_templates/rule_ok_notification.tmpl +19 -0
- rucio-32.8.6.data/data/rucio/etc/rse-accounts.cfg.template +25 -0
- rucio-32.8.6.data/data/rucio/etc/rucio.cfg.atlas.client.template +42 -0
- rucio-32.8.6.data/data/rucio/etc/rucio.cfg.template +257 -0
- rucio-32.8.6.data/data/rucio/etc/rucio_multi_vo.cfg.template +234 -0
- rucio-32.8.6.data/data/rucio/requirements.txt +55 -0
- rucio-32.8.6.data/data/rucio/tools/bootstrap.py +34 -0
- rucio-32.8.6.data/data/rucio/tools/merge_rucio_configs.py +147 -0
- rucio-32.8.6.data/data/rucio/tools/reset_database.py +40 -0
- rucio-32.8.6.data/scripts/rucio +2540 -0
- rucio-32.8.6.data/scripts/rucio-abacus-account +75 -0
- rucio-32.8.6.data/scripts/rucio-abacus-collection-replica +47 -0
- rucio-32.8.6.data/scripts/rucio-abacus-rse +79 -0
- rucio-32.8.6.data/scripts/rucio-admin +2434 -0
- rucio-32.8.6.data/scripts/rucio-atropos +61 -0
- rucio-32.8.6.data/scripts/rucio-auditor +199 -0
- rucio-32.8.6.data/scripts/rucio-automatix +51 -0
- rucio-32.8.6.data/scripts/rucio-bb8 +58 -0
- rucio-32.8.6.data/scripts/rucio-c3po +86 -0
- rucio-32.8.6.data/scripts/rucio-cache-client +135 -0
- rucio-32.8.6.data/scripts/rucio-cache-consumer +43 -0
- rucio-32.8.6.data/scripts/rucio-conveyor-finisher +59 -0
- rucio-32.8.6.data/scripts/rucio-conveyor-poller +67 -0
- rucio-32.8.6.data/scripts/rucio-conveyor-preparer +38 -0
- rucio-32.8.6.data/scripts/rucio-conveyor-receiver +44 -0
- rucio-32.8.6.data/scripts/rucio-conveyor-stager +77 -0
- rucio-32.8.6.data/scripts/rucio-conveyor-submitter +140 -0
- rucio-32.8.6.data/scripts/rucio-conveyor-throttler +105 -0
- rucio-32.8.6.data/scripts/rucio-dark-reaper +54 -0
- rucio-32.8.6.data/scripts/rucio-dumper +159 -0
- rucio-32.8.6.data/scripts/rucio-follower +45 -0
- rucio-32.8.6.data/scripts/rucio-hermes +55 -0
- rucio-32.8.6.data/scripts/rucio-judge-cleaner +90 -0
- rucio-32.8.6.data/scripts/rucio-judge-evaluator +138 -0
- rucio-32.8.6.data/scripts/rucio-judge-injector +45 -0
- rucio-32.8.6.data/scripts/rucio-judge-repairer +45 -0
- rucio-32.8.6.data/scripts/rucio-kronos +45 -0
- rucio-32.8.6.data/scripts/rucio-light-reaper +53 -0
- rucio-32.8.6.data/scripts/rucio-minos +54 -0
- rucio-32.8.6.data/scripts/rucio-minos-temporary-expiration +51 -0
- rucio-32.8.6.data/scripts/rucio-necromancer +121 -0
- rucio-32.8.6.data/scripts/rucio-oauth-manager +64 -0
- rucio-32.8.6.data/scripts/rucio-reaper +84 -0
- rucio-32.8.6.data/scripts/rucio-replica-recoverer +249 -0
- rucio-32.8.6.data/scripts/rucio-storage-consistency-actions +75 -0
- rucio-32.8.6.data/scripts/rucio-transmogrifier +78 -0
- rucio-32.8.6.data/scripts/rucio-undertaker +77 -0
- rucio-32.8.6.dist-info/METADATA +83 -0
- rucio-32.8.6.dist-info/RECORD +481 -0
- rucio-32.8.6.dist-info/WHEEL +5 -0
- rucio-32.8.6.dist-info/licenses/AUTHORS.rst +94 -0
- rucio-32.8.6.dist-info/licenses/LICENSE +201 -0
- rucio-32.8.6.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Copyright European Organization for Nuclear Research (CERN) since 2012
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
|
|
16
|
+
import itertools
|
|
17
|
+
import json
|
|
18
|
+
import logging
|
|
19
|
+
import re
|
|
20
|
+
from configparser import NoOptionError, NoSectionError
|
|
21
|
+
from functools import wraps
|
|
22
|
+
from time import time
|
|
23
|
+
from typing import TYPE_CHECKING
|
|
24
|
+
|
|
25
|
+
import flask
|
|
26
|
+
from flask.views import MethodView
|
|
27
|
+
from werkzeug.datastructures import Headers
|
|
28
|
+
from werkzeug.exceptions import HTTPException
|
|
29
|
+
from werkzeug.wrappers import Request, Response
|
|
30
|
+
|
|
31
|
+
from rucio.api.authentication import validate_auth_token
|
|
32
|
+
from rucio.common import config
|
|
33
|
+
from rucio.common.exception import DatabaseException, RucioException, CannotAuthenticate, UnsupportedRequestedContentType
|
|
34
|
+
from rucio.common.schema import get_schema_value
|
|
35
|
+
from rucio.common.utils import generate_uuid, render_json
|
|
36
|
+
from rucio.core.vo import map_vo
|
|
37
|
+
|
|
38
|
+
if TYPE_CHECKING:
|
|
39
|
+
from collections.abc import Callable, Iterable, Sequence
|
|
40
|
+
from typing import Optional, Union, Any
|
|
41
|
+
|
|
42
|
+
HeadersType = Union[Headers, dict[str, str], Sequence[tuple[str, str]]]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class CORSMiddleware(object):
|
|
46
|
+
"""
|
|
47
|
+
WebUI 2.0 makes preflight requests to the API, which are not handled by the API.
|
|
48
|
+
This middleware intercepts the preflight OPTIONS requests and returns a 200 OK response.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def __init__(self, app: flask.Flask) -> None:
|
|
52
|
+
self.app = app
|
|
53
|
+
|
|
54
|
+
def __call__(self, environ: dict, start_response: 'Callable') -> 'Union[Response, Iterable[bytes]]':
|
|
55
|
+
request: Request = Request(environ)
|
|
56
|
+
|
|
57
|
+
if request.environ.get('REQUEST_METHOD') == 'OPTIONS':
|
|
58
|
+
try:
|
|
59
|
+
webui_urls = config.config_get_list('webui', 'urls')
|
|
60
|
+
except (NoOptionError, NoSectionError, RuntimeError) as error:
|
|
61
|
+
logging.exception('Could not get webui urls from config file')
|
|
62
|
+
return str(error), 500
|
|
63
|
+
if request.origin in webui_urls:
|
|
64
|
+
response: Response = Response(status=200)
|
|
65
|
+
response.headers['Access-Control-Allow-Origin'] = request.origin
|
|
66
|
+
response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
|
|
67
|
+
response.headers['Access-Control-Allow-Headers'] = '*'
|
|
68
|
+
response.headers['Access-Control-Allow-Credentials'] = 'true'
|
|
69
|
+
return response(environ, start_response)
|
|
70
|
+
response: Response = Response(status=403)
|
|
71
|
+
return response(environ, start_response)
|
|
72
|
+
|
|
73
|
+
# bypass this middleware for non-OPTIONS requests
|
|
74
|
+
return self.app(environ, start_response)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class ErrorHandlingMethodView(MethodView):
|
|
78
|
+
"""
|
|
79
|
+
Special MethodView that handles generic RucioExceptions and more generic
|
|
80
|
+
Exceptions for all defined methods automatically.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
def get_headers(self) -> "Optional[HeadersType]":
|
|
84
|
+
"""Can be overridden to add headers to generic error responses."""
|
|
85
|
+
return None
|
|
86
|
+
|
|
87
|
+
def dispatch_request(self, *args, **kwargs):
|
|
88
|
+
headers = self.get_headers() or None
|
|
89
|
+
try:
|
|
90
|
+
return super(ErrorHandlingMethodView, self).dispatch_request(*args, **kwargs)
|
|
91
|
+
except HTTPException:
|
|
92
|
+
raise
|
|
93
|
+
except DatabaseException as error:
|
|
94
|
+
if 'QueuePool' in str(error):
|
|
95
|
+
msg = f'DatabaseException in {self.__class__.__module__} {self.__class__.__name__} {flask.request.method}'
|
|
96
|
+
# logged, because this should be the __exception__
|
|
97
|
+
logging.debug(msg, exc_info=True)
|
|
98
|
+
return generate_http_error_flask(
|
|
99
|
+
status_code=503,
|
|
100
|
+
exc=error.__class__.__name__,
|
|
101
|
+
exc_msg=('Currently there are too many requests for the Rucio '
|
|
102
|
+
'servers to handle. Please try again in a few minutes.'),
|
|
103
|
+
headers=headers
|
|
104
|
+
)
|
|
105
|
+
else:
|
|
106
|
+
msg = f'DatabaseException in {self.__class__.__module__} {self.__class__.__name__} {flask.request.method}'
|
|
107
|
+
logging.debug(msg, exc_info=True)
|
|
108
|
+
return generate_http_error_flask(
|
|
109
|
+
status_code=500,
|
|
110
|
+
exc=error.__class__.__name__,
|
|
111
|
+
exc_msg='An unknown Database Exception has ocurred.',
|
|
112
|
+
headers=headers
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
except RucioException as error:
|
|
116
|
+
# should be caught in the flask view and generate_http_error_flask with a proper HTTP status code returned
|
|
117
|
+
msg = f'Uncaught RucioException in {self.__class__.__module__} {self.__class__.__name__} {flask.request.method}'
|
|
118
|
+
# logged, because this should be the __exception__
|
|
119
|
+
logging.debug(msg, exc_info=True)
|
|
120
|
+
return generate_http_error_flask(
|
|
121
|
+
status_code=500,
|
|
122
|
+
exc=error.__class__.__name__,
|
|
123
|
+
exc_msg=error.args[0],
|
|
124
|
+
headers=headers
|
|
125
|
+
)
|
|
126
|
+
except Exception as error:
|
|
127
|
+
# logged, because this means a programming error
|
|
128
|
+
logging.exception("Internal Error")
|
|
129
|
+
if headers:
|
|
130
|
+
return str(error), 500, headers
|
|
131
|
+
else:
|
|
132
|
+
return str(error), 500
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def request_auth_env():
|
|
136
|
+
if flask.request.environ.get('REQUEST_METHOD') == 'OPTIONS':
|
|
137
|
+
return '', 200
|
|
138
|
+
|
|
139
|
+
auth_token = flask.request.headers.get('X-Rucio-Auth-Token', default=None)
|
|
140
|
+
|
|
141
|
+
try:
|
|
142
|
+
auth = validate_auth_token(auth_token)
|
|
143
|
+
except CannotAuthenticate:
|
|
144
|
+
return generate_http_error_flask(401, CannotAuthenticate.__name__, 'Cannot authenticate with given credentials')
|
|
145
|
+
except RucioException as error:
|
|
146
|
+
return generate_http_error_flask(500, error.__class__.__name__, error.args[0])
|
|
147
|
+
except Exception:
|
|
148
|
+
logging.exception('Internal error in validate_auth_token')
|
|
149
|
+
return 'Internal Error', 500
|
|
150
|
+
|
|
151
|
+
flask.request.environ['vo'] = auth.get('vo', 'def')
|
|
152
|
+
flask.request.environ['issuer'] = auth.get('account')
|
|
153
|
+
flask.request.environ['identity'] = auth.get('identity')
|
|
154
|
+
flask.request.environ['request_id'] = generate_uuid()
|
|
155
|
+
flask.request.environ['start_time'] = time()
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def response_headers(response):
|
|
159
|
+
response.headers['Access-Control-Allow-Origin'] = flask.request.environ.get('HTTP_ORIGIN')
|
|
160
|
+
response.headers['Access-Control-Allow-Headers'] = flask.request.environ.get('HTTP_ACCESS_CONTROL_REQUEST_HEADERS')
|
|
161
|
+
response.headers['Access-Control-Allow-Methods'] = '*'
|
|
162
|
+
response.headers['Access-Control-Allow-Credentials'] = 'true'
|
|
163
|
+
|
|
164
|
+
if flask.request.environ.get('REQUEST_METHOD') == 'GET':
|
|
165
|
+
response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
|
|
166
|
+
response.headers['Cache-Control'] = 'post-check=0, pre-check=0'
|
|
167
|
+
response.headers['Pragma'] = 'no-cache'
|
|
168
|
+
|
|
169
|
+
return response
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def check_accept_header_wrapper_flask(supported_content_types):
|
|
173
|
+
""" Decorator to check if an endpoint supports the requested content type. """
|
|
174
|
+
|
|
175
|
+
def wrapper(f):
|
|
176
|
+
@wraps(f)
|
|
177
|
+
def decorated(*args, **kwargs):
|
|
178
|
+
if not flask.request.accept_mimetypes.provided:
|
|
179
|
+
# accept anything, if Accept header is not provided
|
|
180
|
+
return f(*args, **kwargs)
|
|
181
|
+
|
|
182
|
+
for supported in supported_content_types:
|
|
183
|
+
if supported in flask.request.accept_mimetypes:
|
|
184
|
+
return f(*args, **kwargs)
|
|
185
|
+
|
|
186
|
+
# none matched..
|
|
187
|
+
return generate_http_error_flask(
|
|
188
|
+
status_code=406,
|
|
189
|
+
exc=UnsupportedRequestedContentType.__name__,
|
|
190
|
+
exc_msg=f'The requested content type {flask.request.environ.get("HTTP_ACCEPT")} is not supported. Use {supported_content_types}.'
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
return decorated
|
|
194
|
+
|
|
195
|
+
return wrapper
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def parse_scope_name(scope_name, vo):
|
|
199
|
+
"""
|
|
200
|
+
Parses the given scope_name according to the schema's
|
|
201
|
+
SCOPE_NAME_REGEXP and returns a (scope, name) tuple.
|
|
202
|
+
|
|
203
|
+
:param scope_name: the scope_name string to be parsed.
|
|
204
|
+
:param vo: the vo currently in use.
|
|
205
|
+
:raises ValueError: when scope_name could not be parsed.
|
|
206
|
+
:returns: a (scope, name) tuple.
|
|
207
|
+
"""
|
|
208
|
+
# why again does that regex start with a slash?
|
|
209
|
+
scope_name = re.match(get_schema_value('SCOPE_NAME_REGEXP', vo), '/' + scope_name)
|
|
210
|
+
if scope_name is None:
|
|
211
|
+
raise ValueError('cannot parse scope and name')
|
|
212
|
+
return scope_name.group(1, 2)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def try_stream(generator, content_type=None) -> "flask.Response":
|
|
216
|
+
"""
|
|
217
|
+
Peeks at the first element of the passed generator and raises
|
|
218
|
+
an error, if yielding raises. Otherwise returns
|
|
219
|
+
a flask.Response object.
|
|
220
|
+
|
|
221
|
+
:param generator: a generator function or an iterator.
|
|
222
|
+
:param content_type: the response's Content-Type.
|
|
223
|
+
'application/x-json-stream' by default.
|
|
224
|
+
:returns: a response object with the specified Content-Type.
|
|
225
|
+
"""
|
|
226
|
+
if not content_type:
|
|
227
|
+
content_type = 'application/x-json-stream'
|
|
228
|
+
|
|
229
|
+
it = iter(generator)
|
|
230
|
+
try:
|
|
231
|
+
peek = next(it)
|
|
232
|
+
return flask.Response(flask.stream_with_context(itertools.chain((peek,), it)), content_type=content_type)
|
|
233
|
+
except StopIteration:
|
|
234
|
+
return flask.Response('', content_type=content_type)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def error_headers(exc_cls: str, exc_msg):
|
|
238
|
+
def strip_newlines(msg):
|
|
239
|
+
if msg is None:
|
|
240
|
+
return None
|
|
241
|
+
|
|
242
|
+
return msg.replace('\n', ' ').replace('\r', ' ')
|
|
243
|
+
|
|
244
|
+
exc_msg = strip_newlines(exc_msg)
|
|
245
|
+
if exc_msg:
|
|
246
|
+
# Truncate too long exc_msg
|
|
247
|
+
oldlen = len(exc_msg)
|
|
248
|
+
exc_msg = exc_msg[:min(oldlen, 125)]
|
|
249
|
+
if len(exc_msg) != oldlen:
|
|
250
|
+
exc_msg = exc_msg + '...'
|
|
251
|
+
return {
|
|
252
|
+
'ExceptionClass': strip_newlines(exc_cls),
|
|
253
|
+
'ExceptionMessage': exc_msg
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def _error_response(exc_cls, exc_msg):
|
|
258
|
+
data = {'ExceptionClass': exc_cls,
|
|
259
|
+
'ExceptionMessage': exc_msg}
|
|
260
|
+
headers = {'Content-Type': 'application/octet-stream'}
|
|
261
|
+
headers.update(error_headers(exc_cls=exc_cls, exc_msg=exc_msg))
|
|
262
|
+
return data, headers
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def generate_http_error_flask(
|
|
266
|
+
status_code: "int",
|
|
267
|
+
exc: "Union[str, BaseException]",
|
|
268
|
+
exc_msg: "Optional[str]" = None,
|
|
269
|
+
headers: "Optional[HeadersType]" = None,
|
|
270
|
+
) -> "flask.Response":
|
|
271
|
+
"""Utitily function to generate a complete HTTP error response.
|
|
272
|
+
|
|
273
|
+
:param status_code: The HTTP status code to generate a response for.
|
|
274
|
+
:param exc: The name of the exception class or a RucioException object.
|
|
275
|
+
:param exc_msg: The error message.
|
|
276
|
+
:param headers: any default headers to send along.
|
|
277
|
+
:returns: a response object representing the error.
|
|
278
|
+
"""
|
|
279
|
+
if isinstance(exc, BaseException):
|
|
280
|
+
if not exc_msg and exc.args and exc.args[0]:
|
|
281
|
+
exc_msg = exc.args[0]
|
|
282
|
+
exc_cls = exc.__class__.__name__
|
|
283
|
+
else:
|
|
284
|
+
exc_cls = str(exc)
|
|
285
|
+
exc_msg = str(exc_msg)
|
|
286
|
+
|
|
287
|
+
data, prioheaders = _error_response(exc_cls, exc_msg)
|
|
288
|
+
headers = Headers(headers)
|
|
289
|
+
headers.extend(prioheaders)
|
|
290
|
+
try:
|
|
291
|
+
return flask.Response(
|
|
292
|
+
status=status_code,
|
|
293
|
+
headers=headers,
|
|
294
|
+
content_type=prioheaders['Content-Type'],
|
|
295
|
+
response=render_json(**data),
|
|
296
|
+
)
|
|
297
|
+
except Exception:
|
|
298
|
+
logging.exception(f'Cannot create generate_http_error_flask response with {data}')
|
|
299
|
+
raise
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def json_parameters(json_loads: "Callable[[str], Any]" = json.loads, optional: bool = False) -> dict:
|
|
303
|
+
"""
|
|
304
|
+
Returns the JSON parameters from the current request's body as dict.
|
|
305
|
+
"""
|
|
306
|
+
if optional:
|
|
307
|
+
kwargs = {'default': {}}
|
|
308
|
+
else:
|
|
309
|
+
kwargs = {}
|
|
310
|
+
return json_parse(types=(dict, ), json_loads=json_loads, **kwargs)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def json_list(json_loads: "Callable[[str], Any]" = json.loads, optional: bool = False) -> list:
|
|
314
|
+
"""
|
|
315
|
+
Returns the JSON array from the current request's body as list.
|
|
316
|
+
"""
|
|
317
|
+
if optional:
|
|
318
|
+
kwargs = {'default': []}
|
|
319
|
+
else:
|
|
320
|
+
kwargs = {}
|
|
321
|
+
return json_parse(types=(list, ), json_loads=json_loads, **kwargs)
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def json_parse(types: tuple, json_loads: "Callable[[str], Any]" = json.loads, **kwargs):
|
|
325
|
+
def clstostr(cls):
|
|
326
|
+
if cls.__name__ == "dict":
|
|
327
|
+
return "dictionary"
|
|
328
|
+
else:
|
|
329
|
+
return cls.__name__
|
|
330
|
+
|
|
331
|
+
def typestostr(_types: tuple):
|
|
332
|
+
return " or ".join(map(clstostr, _types))
|
|
333
|
+
|
|
334
|
+
data = flask.request.get_data(as_text=True)
|
|
335
|
+
if 'default' in kwargs and not data:
|
|
336
|
+
return kwargs['default']
|
|
337
|
+
try:
|
|
338
|
+
body = json_loads(data)
|
|
339
|
+
if not isinstance(body, types):
|
|
340
|
+
flask.abort(
|
|
341
|
+
generate_http_error_flask(
|
|
342
|
+
status_code=400,
|
|
343
|
+
exc=TypeError.__name__,
|
|
344
|
+
exc_msg='body must be a json ' + typestostr(types)
|
|
345
|
+
)
|
|
346
|
+
)
|
|
347
|
+
return body
|
|
348
|
+
except json.JSONDecodeError:
|
|
349
|
+
flask.abort(
|
|
350
|
+
generate_http_error_flask(
|
|
351
|
+
status_code=400,
|
|
352
|
+
exc=ValueError.__name__,
|
|
353
|
+
exc_msg='cannot decode json parameter ' + typestostr(types)
|
|
354
|
+
)
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def param_get(parameters: dict, name: str, **kwargs):
|
|
359
|
+
if 'default' in kwargs:
|
|
360
|
+
return parameters.get(name, kwargs['default'])
|
|
361
|
+
else:
|
|
362
|
+
if name not in parameters:
|
|
363
|
+
flask.abort(
|
|
364
|
+
generate_http_error_flask(
|
|
365
|
+
status_code=400,
|
|
366
|
+
exc=KeyError.__name__,
|
|
367
|
+
exc_msg=f"'{name}' not defined"
|
|
368
|
+
)
|
|
369
|
+
)
|
|
370
|
+
return parameters[name]
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def extract_vo(headers: "HeadersType") -> "str":
|
|
374
|
+
""" Extract the VO name from the given request.headers object and
|
|
375
|
+
does any name mapping. Returns the short VO name or raise a
|
|
376
|
+
flask.abort if the VO name doesn't meet the name specification.
|
|
377
|
+
|
|
378
|
+
:papam headers: The request.headers object for the current request.
|
|
379
|
+
:returns: a string containing the short VO name.
|
|
380
|
+
"""
|
|
381
|
+
try:
|
|
382
|
+
return map_vo(headers.get('X-Rucio-VO', default='def'))
|
|
383
|
+
except RucioException as err:
|
|
384
|
+
# VO Name doesn't match allowed spec
|
|
385
|
+
flask.abort(generate_http_error_flask(status_code=400, exc=err))
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Copyright European Organization for Nuclear Research (CERN) since 2012
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
|
|
16
|
+
from flask import Flask, request as request, jsonify
|
|
17
|
+
|
|
18
|
+
from rucio.api import config
|
|
19
|
+
from rucio.common.exception import ConfigurationError, AccessDenied, ConfigNotFound
|
|
20
|
+
from rucio.web.rest.flaskapi.authenticated_bp import AuthenticatedBlueprint
|
|
21
|
+
from rucio.web.rest.flaskapi.v1.common import response_headers, check_accept_header_wrapper_flask, \
|
|
22
|
+
generate_http_error_flask, ErrorHandlingMethodView, json_parameters
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Config(ErrorHandlingMethodView):
|
|
26
|
+
""" REST API for full configuration. """
|
|
27
|
+
|
|
28
|
+
@check_accept_header_wrapper_flask(['application/json'])
|
|
29
|
+
def get(self):
|
|
30
|
+
"""
|
|
31
|
+
---
|
|
32
|
+
summary: List
|
|
33
|
+
description: List the full configuration.
|
|
34
|
+
tags:
|
|
35
|
+
- Config
|
|
36
|
+
responses:
|
|
37
|
+
200:
|
|
38
|
+
description: OK
|
|
39
|
+
content:
|
|
40
|
+
application/json:
|
|
41
|
+
schema:
|
|
42
|
+
description: A dict with the sections as keys and a dict with the configuration as value.
|
|
43
|
+
type: object
|
|
44
|
+
401:
|
|
45
|
+
description: Invalid Auth Token
|
|
46
|
+
406:
|
|
47
|
+
description: Not acceptable
|
|
48
|
+
"""
|
|
49
|
+
res = {}
|
|
50
|
+
for section in config.sections(issuer=request.environ.get('issuer'), vo=request.environ.get('vo')):
|
|
51
|
+
res[section] = {}
|
|
52
|
+
for item in config.items(section, issuer=request.environ.get('issuer'), vo=request.environ.get('vo')):
|
|
53
|
+
res[section][item[0]] = item[1]
|
|
54
|
+
|
|
55
|
+
return jsonify(res), 200
|
|
56
|
+
|
|
57
|
+
def post(self):
|
|
58
|
+
"""
|
|
59
|
+
---
|
|
60
|
+
summary: Create
|
|
61
|
+
description: Create or set the configuration option in the requested section.
|
|
62
|
+
tags:
|
|
63
|
+
- Config
|
|
64
|
+
requestBody:
|
|
65
|
+
content:
|
|
66
|
+
'application/json':
|
|
67
|
+
schema:
|
|
68
|
+
description: "The request body is expected to contain a json {'section': {'option': 'value'}}."
|
|
69
|
+
type: object
|
|
70
|
+
responses:
|
|
71
|
+
201:
|
|
72
|
+
description: OK
|
|
73
|
+
content:
|
|
74
|
+
application/json:
|
|
75
|
+
schema:
|
|
76
|
+
type: string
|
|
77
|
+
enum: ['Created']
|
|
78
|
+
401:
|
|
79
|
+
description: Invalid Auth Token
|
|
80
|
+
400:
|
|
81
|
+
description: The input data was incomplete or invalid
|
|
82
|
+
500:
|
|
83
|
+
description: Configuration error
|
|
84
|
+
"""
|
|
85
|
+
parameters = json_parameters()
|
|
86
|
+
for section, section_config in parameters.items():
|
|
87
|
+
if not isinstance(section_config, dict):
|
|
88
|
+
return generate_http_error_flask(400, ValueError.__name__, '')
|
|
89
|
+
for option, value in section_config.items():
|
|
90
|
+
try:
|
|
91
|
+
config.set(section=section, option=option, value=value, issuer=request.environ.get('issuer'), vo=request.environ.get('vo'))
|
|
92
|
+
except ConfigurationError:
|
|
93
|
+
return generate_http_error_flask(400, 'ConfigurationError', f"Could not set value '{value}' for section '{section}' option '{option}'")
|
|
94
|
+
return 'Created', 201
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class Section(ErrorHandlingMethodView):
|
|
98
|
+
""" REST API for the sections in the configuration. """
|
|
99
|
+
|
|
100
|
+
@check_accept_header_wrapper_flask(['application/json'])
|
|
101
|
+
def get(self, section):
|
|
102
|
+
"""
|
|
103
|
+
---
|
|
104
|
+
summary: List Sections
|
|
105
|
+
tags:
|
|
106
|
+
- Config
|
|
107
|
+
parameters:
|
|
108
|
+
- name: section
|
|
109
|
+
in: path
|
|
110
|
+
description: The section to return.
|
|
111
|
+
schema:
|
|
112
|
+
type: string
|
|
113
|
+
style: simple
|
|
114
|
+
requestBody:
|
|
115
|
+
content:
|
|
116
|
+
'application/json':
|
|
117
|
+
schema:
|
|
118
|
+
type: object
|
|
119
|
+
required:
|
|
120
|
+
- bytes
|
|
121
|
+
properties:
|
|
122
|
+
bytes:
|
|
123
|
+
description: The new limit in bytes.
|
|
124
|
+
type: integer
|
|
125
|
+
responses:
|
|
126
|
+
200:
|
|
127
|
+
description: OK
|
|
128
|
+
content:
|
|
129
|
+
application/json:
|
|
130
|
+
schema:
|
|
131
|
+
description: Dictionary of section options.
|
|
132
|
+
type: object
|
|
133
|
+
401:
|
|
134
|
+
description: Invalid Auth Token
|
|
135
|
+
404:
|
|
136
|
+
description: Config not found
|
|
137
|
+
406:
|
|
138
|
+
description: Not acceptable
|
|
139
|
+
"""
|
|
140
|
+
res = {}
|
|
141
|
+
for item in config.items(section, issuer=request.environ.get('issuer'), vo=request.environ.get('vo')):
|
|
142
|
+
res[item[0]] = item[1]
|
|
143
|
+
|
|
144
|
+
if res == {}:
|
|
145
|
+
return generate_http_error_flask(
|
|
146
|
+
status_code=404,
|
|
147
|
+
exc=ConfigNotFound.__name__,
|
|
148
|
+
exc_msg=f"No configuration found for section '{section}'"
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
return jsonify(res), 200
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class OptionGetDel(ErrorHandlingMethodView):
|
|
155
|
+
""" REST API for reading or deleting the options in the configuration. """
|
|
156
|
+
|
|
157
|
+
@check_accept_header_wrapper_flask(['application/json'])
|
|
158
|
+
def get(self, section, option):
|
|
159
|
+
"""
|
|
160
|
+
---
|
|
161
|
+
summary: Get option
|
|
162
|
+
description: Returns the value of an option
|
|
163
|
+
tags:
|
|
164
|
+
- Config
|
|
165
|
+
parameters:
|
|
166
|
+
- name: section
|
|
167
|
+
in: path
|
|
168
|
+
description: The section.
|
|
169
|
+
schema:
|
|
170
|
+
type: string
|
|
171
|
+
style: simple
|
|
172
|
+
- name: option
|
|
173
|
+
in: path
|
|
174
|
+
description: The option of the section.
|
|
175
|
+
schema:
|
|
176
|
+
type: string
|
|
177
|
+
style: simple
|
|
178
|
+
responses:
|
|
179
|
+
200:
|
|
180
|
+
description: OK
|
|
181
|
+
content:
|
|
182
|
+
application/json:
|
|
183
|
+
schema:
|
|
184
|
+
description: The value of the option
|
|
185
|
+
type: string
|
|
186
|
+
401:
|
|
187
|
+
description: Invalid Auth Token
|
|
188
|
+
404:
|
|
189
|
+
description: Config not found
|
|
190
|
+
406:
|
|
191
|
+
description: Not acceptable
|
|
192
|
+
"""
|
|
193
|
+
try:
|
|
194
|
+
result = config.get(section=section, option=option, issuer=request.environ.get('issuer'), vo=request.environ.get('vo'))
|
|
195
|
+
return jsonify(result), 200
|
|
196
|
+
except AccessDenied as error:
|
|
197
|
+
return generate_http_error_flask(401, error, f"Access to '{section}' option '{option}' denied")
|
|
198
|
+
except ConfigNotFound as error:
|
|
199
|
+
return generate_http_error_flask(404, error, f"No configuration found for section '{section}' option '{option}'")
|
|
200
|
+
|
|
201
|
+
def delete(self, section, option):
|
|
202
|
+
"""
|
|
203
|
+
---
|
|
204
|
+
summary: Delete option
|
|
205
|
+
description: Delete an option of a section.
|
|
206
|
+
tags:
|
|
207
|
+
- Config
|
|
208
|
+
parameters:
|
|
209
|
+
- name: section
|
|
210
|
+
in: path
|
|
211
|
+
description: The section.
|
|
212
|
+
schema:
|
|
213
|
+
type: string
|
|
214
|
+
style: simple
|
|
215
|
+
- name: option
|
|
216
|
+
in: path
|
|
217
|
+
description: The option of the section.
|
|
218
|
+
schema:
|
|
219
|
+
type: string
|
|
220
|
+
style: simple
|
|
221
|
+
responses:
|
|
222
|
+
200:
|
|
223
|
+
description: OK
|
|
224
|
+
401:
|
|
225
|
+
description: Invalid Auth Token
|
|
226
|
+
"""
|
|
227
|
+
config.remove_option(section=section, option=option, issuer=request.environ.get('issuer'), vo=request.environ.get('vo'))
|
|
228
|
+
return '', 200
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
class OptionSet(ErrorHandlingMethodView):
|
|
232
|
+
""" REST API for setting the options in the configuration. """
|
|
233
|
+
|
|
234
|
+
def put(self, section, option, value):
|
|
235
|
+
"""
|
|
236
|
+
---
|
|
237
|
+
summary: Create value
|
|
238
|
+
description: Create or set the value of an option.
|
|
239
|
+
tags:
|
|
240
|
+
- Config
|
|
241
|
+
parameters:
|
|
242
|
+
- name: section
|
|
243
|
+
in: path
|
|
244
|
+
description: The section.
|
|
245
|
+
schema:
|
|
246
|
+
type: string
|
|
247
|
+
style: simple
|
|
248
|
+
- name: option
|
|
249
|
+
in: path
|
|
250
|
+
description: The option of the section.
|
|
251
|
+
schema:
|
|
252
|
+
type: string
|
|
253
|
+
style: simple
|
|
254
|
+
- name: value
|
|
255
|
+
in: path
|
|
256
|
+
description: The value to set.
|
|
257
|
+
schema:
|
|
258
|
+
type: string
|
|
259
|
+
style: simple
|
|
260
|
+
responses:
|
|
261
|
+
201:
|
|
262
|
+
description: OK
|
|
263
|
+
content:
|
|
264
|
+
application/json:
|
|
265
|
+
schema:
|
|
266
|
+
type: string
|
|
267
|
+
enum: ['Created']
|
|
268
|
+
401:
|
|
269
|
+
description: Invalid Auth Token
|
|
270
|
+
500:
|
|
271
|
+
description: Value could not be set
|
|
272
|
+
content:
|
|
273
|
+
application/json:
|
|
274
|
+
schema:
|
|
275
|
+
type: string
|
|
276
|
+
enum: ['Could not set value {} for section {} option {}']
|
|
277
|
+
"""
|
|
278
|
+
try:
|
|
279
|
+
config.set(section=section, option=option, value=value, issuer=request.environ.get('issuer'), vo=request.environ.get('vo'))
|
|
280
|
+
return 'Created', 201
|
|
281
|
+
except ConfigurationError as error:
|
|
282
|
+
return generate_http_error_flask(500, error, f"Could not set value '{value}' for section '{section}' option '{option}'")
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def blueprint():
|
|
286
|
+
bp = AuthenticatedBlueprint('config', __name__, url_prefix='/config')
|
|
287
|
+
|
|
288
|
+
option_set_view = OptionSet.as_view('option_set')
|
|
289
|
+
bp.add_url_rule('/<section>/<option>/<value>', view_func=option_set_view, methods=['put', ])
|
|
290
|
+
option_get_del_view = OptionGetDel.as_view('option_get_del')
|
|
291
|
+
bp.add_url_rule('/<section>/<option>', view_func=option_get_del_view, methods=['get', 'delete'])
|
|
292
|
+
section_view = Section.as_view('section')
|
|
293
|
+
bp.add_url_rule('/<section>', view_func=section_view, methods=['get', ])
|
|
294
|
+
config_view = Config.as_view('config')
|
|
295
|
+
bp.add_url_rule('', view_func=config_view, methods=['get', 'post'])
|
|
296
|
+
|
|
297
|
+
bp.after_request(response_headers)
|
|
298
|
+
return bp
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def make_doc():
|
|
302
|
+
""" Only used for sphinx documentation """
|
|
303
|
+
doc_app = Flask(__name__)
|
|
304
|
+
doc_app.register_blueprint(blueprint())
|
|
305
|
+
return doc_app
|