rucio 37.2.0__py3-none-any.whl → 37.4.0__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/cli/rule.py +1 -1
- rucio/client/accountclient.py +205 -60
- rucio/client/accountlimitclient.py +84 -25
- rucio/client/baseclient.py +85 -48
- rucio/client/client.py +49 -41
- rucio/client/configclient.py +36 -13
- rucio/client/credentialclient.py +16 -6
- rucio/client/didclient.py +321 -133
- rucio/client/diracclient.py +13 -6
- rucio/client/downloadclient.py +435 -165
- rucio/client/exportclient.py +8 -2
- rucio/client/fileclient.py +10 -3
- rucio/client/importclient.py +4 -1
- rucio/client/lifetimeclient.py +48 -31
- rucio/client/lockclient.py +22 -7
- rucio/client/metaconventionsclient.py +59 -21
- rucio/client/pingclient.py +3 -1
- rucio/client/replicaclient.py +213 -96
- rucio/client/requestclient.py +123 -16
- rucio/client/rseclient.py +385 -160
- rucio/client/ruleclient.py +147 -51
- rucio/client/scopeclient.py +35 -10
- rucio/client/subscriptionclient.py +60 -27
- rucio/client/touchclient.py +16 -7
- rucio/common/plugins.py +1 -1
- rucio/core/did.py +2 -3
- rucio/core/permission/generic.py +37 -1
- rucio/core/replica.py +6 -6
- rucio/core/rule.py +5 -3
- rucio/daemons/judge/evaluator.py +1 -1
- rucio/db/sqla/util.py +1 -1
- rucio/gateway/authentication.py +58 -88
- rucio/gateway/config.py +63 -75
- rucio/gateway/did.py +245 -329
- rucio/gateway/dirac.py +33 -34
- rucio/gateway/exporter.py +27 -30
- rucio/gateway/importer.py +12 -14
- rucio/gateway/lifetime_exception.py +16 -24
- rucio/gateway/lock.py +27 -40
- rucio/gateway/replica.py +334 -249
- rucio/gateway/request.py +176 -103
- rucio/gateway/rse.py +191 -218
- rucio/gateway/rule.py +115 -146
- rucio/gateway/scope.py +18 -25
- rucio/gateway/subscription.py +90 -108
- rucio/gateway/trace.py +48 -0
- rucio/vcsversion.py +3 -3
- rucio/web/rest/flaskapi/v1/accounts.py +2 -2
- rucio/web/rest/flaskapi/v1/auth.py +15 -0
- rucio/web/rest/flaskapi/v1/common.py +3 -0
- rucio/web/rest/flaskapi/v1/config.py +7 -7
- rucio/web/rest/flaskapi/v1/dids.py +55 -55
- rucio/web/rest/flaskapi/v1/dirac.py +2 -2
- rucio/web/rest/flaskapi/v1/export.py +1 -1
- rucio/web/rest/flaskapi/v1/import.py +1 -1
- rucio/web/rest/flaskapi/v1/lifetime_exceptions.py +5 -5
- rucio/web/rest/flaskapi/v1/locks.py +4 -4
- rucio/web/rest/flaskapi/v1/main.py +17 -10
- rucio/web/rest/flaskapi/v1/redirect.py +1 -1
- rucio/web/rest/flaskapi/v1/replicas.py +30 -29
- rucio/web/rest/flaskapi/v1/requests.py +211 -20
- rucio/web/rest/flaskapi/v1/rses.py +37 -37
- rucio/web/rest/flaskapi/v1/rules.py +15 -15
- rucio/web/rest/flaskapi/v1/scopes.py +3 -3
- rucio/web/rest/flaskapi/v1/subscriptions.py +9 -9
- rucio/web/rest/flaskapi/v1/traces.py +75 -77
- {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/etc/rucio.cfg.template +0 -1
- {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/etc/rucio_multi_vo.cfg.template +0 -1
- {rucio-37.2.0.dist-info → rucio-37.4.0.dist-info}/METADATA +1 -1
- {rucio-37.2.0.dist-info → rucio-37.4.0.dist-info}/RECORD +127 -126
- {rucio-37.2.0.dist-info → rucio-37.4.0.dist-info}/WHEEL +1 -1
- {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/etc/alembic.ini.template +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/etc/alembic_offline.ini.template +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/etc/globus-config.yml.template +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/etc/ldap.cfg.template +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/etc/mail_templates/rule_approval_request.tmpl +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/etc/mail_templates/rule_approved_admin.tmpl +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/etc/mail_templates/rule_approved_user.tmpl +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/etc/mail_templates/rule_denied_admin.tmpl +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/etc/mail_templates/rule_denied_user.tmpl +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/etc/mail_templates/rule_ok_notification.tmpl +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/etc/rse-accounts.cfg.template +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/etc/rucio.cfg.atlas.client.template +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/requirements.server.txt +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/tools/bootstrap.py +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/tools/merge_rucio_configs.py +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/data/rucio/tools/reset_database.py +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-abacus-account +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-abacus-collection-replica +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-abacus-rse +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-admin +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-atropos +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-auditor +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-automatix +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-bb8 +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-cache-client +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-cache-consumer +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-conveyor-finisher +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-conveyor-poller +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-conveyor-preparer +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-conveyor-receiver +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-conveyor-stager +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-conveyor-submitter +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-conveyor-throttler +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-dark-reaper +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-dumper +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-follower +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-hermes +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-judge-cleaner +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-judge-evaluator +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-judge-injector +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-judge-repairer +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-kronos +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-minos +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-minos-temporary-expiration +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-necromancer +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-oauth-manager +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-reaper +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-replica-recoverer +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-rse-decommissioner +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-storage-consistency-actions +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-transmogrifier +0 -0
- {rucio-37.2.0.data → rucio-37.4.0.data}/scripts/rucio-undertaker +0 -0
- {rucio-37.2.0.dist-info → rucio-37.4.0.dist-info}/licenses/AUTHORS.rst +0 -0
- {rucio-37.2.0.dist-info → rucio-37.4.0.dist-info}/licenses/LICENSE +0 -0
- {rucio-37.2.0.dist-info → rucio-37.4.0.dist-info}/top_level.txt +0 -0
rucio/gateway/subscription.py
CHANGED
|
@@ -20,19 +20,17 @@ from rucio.common.exception import AccessDenied, InvalidObject
|
|
|
20
20
|
from rucio.common.schema import validate_schema
|
|
21
21
|
from rucio.common.types import InternalAccount, InternalScope
|
|
22
22
|
from rucio.core import subscription
|
|
23
|
-
from rucio.db.sqla.
|
|
23
|
+
from rucio.db.sqla.constants import DatabaseOperationType
|
|
24
|
+
from rucio.db.sqla.session import db_session
|
|
24
25
|
from rucio.gateway.permission import has_permission
|
|
25
26
|
|
|
26
27
|
if TYPE_CHECKING:
|
|
27
28
|
from collections.abc import Iterator
|
|
28
29
|
|
|
29
|
-
from sqlalchemy.orm import Session
|
|
30
|
-
|
|
31
30
|
|
|
32
31
|
SubscriptionRuleState = namedtuple('SubscriptionRuleState', ['account', 'name', 'state', 'count'])
|
|
33
32
|
|
|
34
33
|
|
|
35
|
-
@transactional_session
|
|
36
34
|
def add_subscription(
|
|
37
35
|
name: str,
|
|
38
36
|
account: str,
|
|
@@ -42,11 +40,9 @@ def add_subscription(
|
|
|
42
40
|
lifetime: Union[int, Literal[False]],
|
|
43
41
|
retroactive: bool,
|
|
44
42
|
dry_run: bool,
|
|
43
|
+
issuer: str,
|
|
45
44
|
priority: Optional[int] = None,
|
|
46
|
-
issuer: Optional[str] = None,
|
|
47
45
|
vo: str = 'def',
|
|
48
|
-
*,
|
|
49
|
-
session: "Session"
|
|
50
46
|
) -> str:
|
|
51
47
|
"""
|
|
52
48
|
Adds a new subscription which will be verified against every new added file and dataset
|
|
@@ -63,53 +59,50 @@ def add_subscription(
|
|
|
63
59
|
:param priority: The priority of the subscription
|
|
64
60
|
:param issuer: The account issuing this operation.
|
|
65
61
|
:param vo: The VO to act on.
|
|
66
|
-
:param session: The database session in use.
|
|
67
62
|
:returns: subscription_id
|
|
68
63
|
"""
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
raise InvalidObject('You must specify a rule')
|
|
85
|
-
except ValueError as error:
|
|
86
|
-
raise TypeError(error)
|
|
87
|
-
|
|
88
|
-
internal_account = InternalAccount(account, vo=vo)
|
|
89
|
-
|
|
90
|
-
keys = ['scope', 'account']
|
|
91
|
-
types = [InternalScope, InternalAccount]
|
|
92
|
-
for _key, _type in zip(keys, types):
|
|
93
|
-
if _key in filter_:
|
|
94
|
-
if isinstance(filter_[_key], list):
|
|
95
|
-
filter_[_key] = [_type(val, vo=vo).internal for val in filter_[_key]]
|
|
64
|
+
with db_session(DatabaseOperationType.WRITE) as session:
|
|
65
|
+
auth_result = has_permission(issuer=issuer, vo=vo, action='add_subscription', kwargs={'account': account}, session=session)
|
|
66
|
+
if not auth_result.allowed:
|
|
67
|
+
raise AccessDenied('Account %s can not add subscription. %s' % (issuer, auth_result.message))
|
|
68
|
+
try:
|
|
69
|
+
if filter_:
|
|
70
|
+
if not isinstance(filter_, dict):
|
|
71
|
+
raise TypeError('filter should be a dict')
|
|
72
|
+
validate_schema(name='subscription_filter', obj=filter_, vo=vo)
|
|
73
|
+
if replication_rules:
|
|
74
|
+
if not isinstance(replication_rules, list):
|
|
75
|
+
raise TypeError('replication_rules should be a list')
|
|
76
|
+
else:
|
|
77
|
+
for rule in replication_rules:
|
|
78
|
+
validate_schema(name='activity', obj=rule.get('activity', 'default'), vo=vo)
|
|
96
79
|
else:
|
|
97
|
-
|
|
80
|
+
raise InvalidObject('You must specify a rule')
|
|
81
|
+
except ValueError as error:
|
|
82
|
+
raise TypeError(error)
|
|
83
|
+
|
|
84
|
+
internal_account = InternalAccount(account, vo=vo)
|
|
85
|
+
|
|
86
|
+
keys = ['scope', 'account']
|
|
87
|
+
types = [InternalScope, InternalAccount]
|
|
88
|
+
for _key, _type in zip(keys, types):
|
|
89
|
+
if _key in filter_:
|
|
90
|
+
if isinstance(filter_[_key], list):
|
|
91
|
+
filter_[_key] = [_type(val, vo=vo).internal for val in filter_[_key]]
|
|
92
|
+
else:
|
|
93
|
+
filter_[_key] = _type(filter_[_key], vo=vo).internal
|
|
98
94
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
95
|
+
return subscription.add_subscription(name=name, account=internal_account, filter_=dumps(filter_), replication_rules=dumps(replication_rules),
|
|
96
|
+
comments=comments, lifetime=lifetime, retroactive=retroactive, dry_run=dry_run, priority=priority,
|
|
97
|
+
session=session)
|
|
102
98
|
|
|
103
99
|
|
|
104
|
-
@transactional_session
|
|
105
100
|
def update_subscription(
|
|
106
101
|
name: str,
|
|
107
102
|
account: str,
|
|
103
|
+
issuer: str,
|
|
108
104
|
metadata: Optional[dict[str, Any]] = None,
|
|
109
|
-
issuer: Optional[str] = None,
|
|
110
105
|
vo: str = 'def',
|
|
111
|
-
*,
|
|
112
|
-
session: "Session"
|
|
113
106
|
) -> None:
|
|
114
107
|
"""
|
|
115
108
|
Updates a subscription
|
|
@@ -119,53 +112,50 @@ def update_subscription(
|
|
|
119
112
|
:param metadata: Dictionary of metadata to update. Supported keys : filter, replication_rules, comments, lifetime, retroactive, dry_run, priority, last_processed
|
|
120
113
|
:param issuer: The account issuing this operation.
|
|
121
114
|
:param vo: The VO to act on.
|
|
122
|
-
:param session: The database session in use.
|
|
123
115
|
:raises: SubscriptionNotFound if subscription is not found
|
|
124
116
|
"""
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
if
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
117
|
+
with db_session(DatabaseOperationType.WRITE) as session:
|
|
118
|
+
auth_result = has_permission(issuer=issuer, vo=vo, action='update_subscription', kwargs={'account': account}, session=session)
|
|
119
|
+
if not auth_result.allowed:
|
|
120
|
+
raise AccessDenied('Account %s can not update subscription. %s' % (issuer, auth_result.message))
|
|
121
|
+
try:
|
|
122
|
+
if not isinstance(metadata, dict):
|
|
123
|
+
raise TypeError('metadata should be a dict')
|
|
124
|
+
if 'filter' in metadata and metadata['filter']:
|
|
125
|
+
if not isinstance(metadata['filter'], dict):
|
|
126
|
+
raise TypeError('filter should be a dict')
|
|
127
|
+
validate_schema(name='subscription_filter', obj=metadata['filter'], vo=vo)
|
|
128
|
+
if 'replication_rules' in metadata and metadata['replication_rules']:
|
|
129
|
+
if not isinstance(metadata['replication_rules'], list):
|
|
130
|
+
raise TypeError('replication_rules should be a list')
|
|
131
|
+
else:
|
|
132
|
+
for rule in metadata['replication_rules']:
|
|
133
|
+
validate_schema(name='activity', obj=rule.get('activity', 'default'), vo=vo)
|
|
134
|
+
except ValueError as error:
|
|
135
|
+
raise TypeError(error)
|
|
143
136
|
|
|
144
|
-
|
|
137
|
+
internal_account = InternalAccount(account, vo=vo)
|
|
145
138
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
139
|
+
if 'filter' in metadata and metadata['filter'] is not None:
|
|
140
|
+
filter_ = metadata['filter']
|
|
141
|
+
keys = ['scope', 'account']
|
|
142
|
+
types = [InternalScope, InternalAccount]
|
|
150
143
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
144
|
+
for _key, _type in zip(keys, types):
|
|
145
|
+
if _key in filter_ and filter_[_key] is not None:
|
|
146
|
+
if isinstance(filter_[_key], list):
|
|
147
|
+
filter_[_key] = [_type(val, vo=vo).internal for val in filter_[_key]]
|
|
148
|
+
else:
|
|
149
|
+
filter_[_key] = _type(filter_[_key], vo=vo).internal
|
|
157
150
|
|
|
158
|
-
|
|
151
|
+
return subscription.update_subscription(name=name, account=internal_account, metadata=metadata, session=session)
|
|
159
152
|
|
|
160
153
|
|
|
161
|
-
@stream_session
|
|
162
154
|
def list_subscriptions(
|
|
163
155
|
name: Optional[str] = None,
|
|
164
156
|
account: Optional[str] = None,
|
|
165
157
|
state: Optional[str] = None,
|
|
166
158
|
vo: str = 'def',
|
|
167
|
-
*,
|
|
168
|
-
session: "Session"
|
|
169
159
|
) -> 'Iterator[dict[str, Any]]':
|
|
170
160
|
"""
|
|
171
161
|
Returns a dictionary with the subscription information :
|
|
@@ -175,7 +165,6 @@ def list_subscriptions(
|
|
|
175
165
|
:param account: Account identifier
|
|
176
166
|
:param state: Filter for subscription state
|
|
177
167
|
:param vo: The VO to act on.
|
|
178
|
-
:param session: The database session in use.
|
|
179
168
|
:returns: Dictionary containing subscription parameter
|
|
180
169
|
:raises: exception.NotFound if subscription is not found
|
|
181
170
|
"""
|
|
@@ -185,85 +174,78 @@ def list_subscriptions(
|
|
|
185
174
|
else:
|
|
186
175
|
internal_account = InternalAccount('*', vo=vo)
|
|
187
176
|
|
|
188
|
-
|
|
177
|
+
with db_session(DatabaseOperationType.READ) as session:
|
|
178
|
+
subs = subscription.list_subscriptions(name, internal_account, state, session=session)
|
|
189
179
|
|
|
190
|
-
|
|
191
|
-
|
|
180
|
+
for sub in subs:
|
|
181
|
+
sub['account'] = sub['account'].external
|
|
192
182
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
183
|
+
if 'filter' in sub:
|
|
184
|
+
fil = loads(sub['filter'])
|
|
185
|
+
if 'account' in fil:
|
|
186
|
+
fil['account'] = [InternalAccount(acc, from_external=False).external for acc in fil['account']]
|
|
187
|
+
if 'scope' in fil:
|
|
188
|
+
fil['scope'] = [InternalScope(sco, from_external=False).external for sco in fil['scope']]
|
|
189
|
+
sub['filter'] = dumps(fil)
|
|
200
190
|
|
|
201
|
-
|
|
191
|
+
yield sub
|
|
202
192
|
|
|
203
193
|
|
|
204
|
-
@stream_session
|
|
205
194
|
def list_subscription_rule_states(
|
|
206
195
|
name: Optional[str] = None,
|
|
207
196
|
account: Optional[str] = None,
|
|
208
197
|
vo: str = 'def',
|
|
209
|
-
*,
|
|
210
|
-
session: "Session"
|
|
211
198
|
) -> 'Iterator[SubscriptionRuleState]':
|
|
212
199
|
"""Returns a list of with the number of rules per state for a subscription.
|
|
213
200
|
|
|
214
201
|
:param name: Name of the subscription
|
|
215
202
|
:param account: Account identifier
|
|
216
203
|
:param vo: The VO to act on.
|
|
217
|
-
:param session: The database session in use.
|
|
218
204
|
:returns: Sequence with SubscriptionRuleState named tuples (account, name, state, count)
|
|
219
205
|
"""
|
|
220
206
|
if account is not None:
|
|
221
207
|
internal_account = InternalAccount(account, vo=vo)
|
|
222
208
|
else:
|
|
223
209
|
internal_account = InternalAccount('*', vo=vo)
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
210
|
+
|
|
211
|
+
with db_session(DatabaseOperationType.READ) as session:
|
|
212
|
+
subs = subscription.list_subscription_rule_states(name, internal_account, session=session)
|
|
213
|
+
|
|
214
|
+
for sub in subs:
|
|
215
|
+
# sub is an immutable Row so return new named tuple with edited entries
|
|
216
|
+
d = sub._asdict()
|
|
217
|
+
d['account'] = d['account'].external
|
|
218
|
+
yield SubscriptionRuleState(**d)
|
|
230
219
|
|
|
231
220
|
|
|
232
|
-
@transactional_session
|
|
233
221
|
def delete_subscription(
|
|
234
222
|
subscription_id: str,
|
|
235
223
|
vo: str = 'def',
|
|
236
|
-
*,
|
|
237
|
-
session: "Session"
|
|
238
224
|
) -> None:
|
|
239
225
|
"""
|
|
240
226
|
Deletes a subscription
|
|
241
227
|
|
|
242
228
|
:param subscription_id: Subscription identifier
|
|
243
229
|
:param vo: The VO of the user issuing command
|
|
244
|
-
:param session: The database session in use.
|
|
245
230
|
"""
|
|
246
231
|
|
|
247
232
|
raise NotImplementedError
|
|
248
233
|
|
|
249
234
|
|
|
250
|
-
@read_session
|
|
251
235
|
def get_subscription_by_id(
|
|
252
236
|
subscription_id: str,
|
|
253
237
|
vo: str = 'def',
|
|
254
|
-
*,
|
|
255
|
-
session: "Session"
|
|
256
238
|
) -> dict[str, Any]:
|
|
257
239
|
"""
|
|
258
240
|
Get a specific subscription by id.
|
|
259
241
|
|
|
260
242
|
:param subscription_id: The subscription_id to select.
|
|
261
243
|
:param vo: The VO of the user issuing command.
|
|
262
|
-
:param session: The database session in use.
|
|
263
244
|
|
|
264
245
|
:raises: SubscriptionNotFound if no Subscription can be found.
|
|
265
246
|
"""
|
|
266
|
-
|
|
247
|
+
with db_session(DatabaseOperationType.READ) as session:
|
|
248
|
+
sub = subscription.get_subscription_by_id(subscription_id, session=session)
|
|
267
249
|
if sub['account'].vo != vo:
|
|
268
250
|
raise AccessDenied('Unable to get subscription')
|
|
269
251
|
|
rucio/gateway/trace.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
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 datetime
|
|
16
|
+
import json
|
|
17
|
+
import uuid
|
|
18
|
+
from typing import Optional
|
|
19
|
+
|
|
20
|
+
from rucio.core.trace import trace as core_trace
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def trace(request: bytes, trace_ip: Optional[str]) -> None:
|
|
24
|
+
"""
|
|
25
|
+
Sends the trace data to trace broker after adding additional fields
|
|
26
|
+
Adds the following fields:
|
|
27
|
+
- 'traceTimeentry': The current UTC timestamp.
|
|
28
|
+
- 'traceTimeentryUnix': The Unix timestamp with microsecond precision.
|
|
29
|
+
- 'traceIp': The client's IP address, either from 'X-Forwarded-For' header or remote address.
|
|
30
|
+
- 'traceId': A unique identifier for the trace, generated as a UUID without hyphens.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
request_data: Request json given by client
|
|
34
|
+
trace_ip: TraceIP, either the client's IP address, or IP from "X-Forwarded-For" header
|
|
35
|
+
"""
|
|
36
|
+
request_data = json.loads(request)
|
|
37
|
+
if isinstance(request_data, dict):
|
|
38
|
+
request_data = [request_data]
|
|
39
|
+
|
|
40
|
+
for item in request_data:
|
|
41
|
+
item["traceIp"] = trace_ip
|
|
42
|
+
# generate entry timestamp
|
|
43
|
+
item["traceTimeentry"] = datetime.datetime.now(datetime.timezone.utc)
|
|
44
|
+
item["traceTimeentryUnix"] = item["traceTimeentry"].timestamp()
|
|
45
|
+
# generate unique ID
|
|
46
|
+
item["traceId"] = str(uuid.uuid4()).replace("-", "").lower()
|
|
47
|
+
|
|
48
|
+
core_trace(payload=item)
|
rucio/vcsversion.py
CHANGED
|
@@ -4,8 +4,8 @@ This file is automatically generated; Do not edit it. :)
|
|
|
4
4
|
'''
|
|
5
5
|
VERSION_INFO = {
|
|
6
6
|
'final': True,
|
|
7
|
-
'version': '37.
|
|
7
|
+
'version': '37.4.0',
|
|
8
8
|
'branch_nick': 'release-37',
|
|
9
|
-
'revision_id': '
|
|
10
|
-
'revno':
|
|
9
|
+
'revision_id': '20eed71b1dd6d1d8e550966b7d40ea730a16c7d8',
|
|
10
|
+
'revno': 13754
|
|
11
11
|
}
|
|
@@ -217,7 +217,7 @@ class Scopes(ErrorHandlingMethodView):
|
|
|
217
217
|
description: Not acceptable
|
|
218
218
|
"""
|
|
219
219
|
try:
|
|
220
|
-
scopes = get_scopes(account, vo=request.environ
|
|
220
|
+
scopes = get_scopes(account, vo=request.environ['vo'])
|
|
221
221
|
except AccountNotFound as error:
|
|
222
222
|
return generate_http_error_flask(404, error)
|
|
223
223
|
|
|
@@ -264,7 +264,7 @@ class Scopes(ErrorHandlingMethodView):
|
|
|
264
264
|
description: Scope already exists.
|
|
265
265
|
"""
|
|
266
266
|
try:
|
|
267
|
-
add_scope(scope, account, issuer=request.environ
|
|
267
|
+
add_scope(scope, account, issuer=request.environ['issuer'], vo=request.environ['vo'])
|
|
268
268
|
except InvalidObject as error:
|
|
269
269
|
return generate_http_error_flask(400, error)
|
|
270
270
|
except AccessDenied as error:
|
|
@@ -916,6 +916,9 @@ class GSS(ErrorHandlingMethodView):
|
|
|
916
916
|
appid = request.headers.get('X-Rucio-AppID', default='unknown')
|
|
917
917
|
ip = request.headers.get('X-Forwarded-For', default=request.remote_addr)
|
|
918
918
|
|
|
919
|
+
if not account or not gsscred:
|
|
920
|
+
return generate_http_error_flask(400, ValueError.__name__, 'Account and REMOTE_USER must be set.')
|
|
921
|
+
|
|
919
922
|
try:
|
|
920
923
|
result = get_auth_token_gss(account, gsscred, appid, ip, vo=vo)
|
|
921
924
|
except AccessDenied:
|
|
@@ -1212,6 +1215,9 @@ class SSH(ErrorHandlingMethodView):
|
|
|
1212
1215
|
appid = request.headers.get('X-Rucio-AppID', default='unknown')
|
|
1213
1216
|
ip = request.headers.get('X-Forwarded-For', default=request.remote_addr)
|
|
1214
1217
|
|
|
1218
|
+
if not account or not signature:
|
|
1219
|
+
return generate_http_error_flask(400, ValueError.__name__, 'Account and SSH signature must be set.')
|
|
1220
|
+
|
|
1215
1221
|
try:
|
|
1216
1222
|
result = get_auth_token_ssh(account, signature, appid, ip, vo=vo)
|
|
1217
1223
|
except AccessDenied:
|
|
@@ -1332,6 +1338,9 @@ class SSHChallengeToken(ErrorHandlingMethodView):
|
|
|
1332
1338
|
appid = request.headers.get('X-Rucio-AppID', default='unknown')
|
|
1333
1339
|
ip = request.headers.get('X-Forwarded-For', default=request.remote_addr)
|
|
1334
1340
|
|
|
1341
|
+
if not account:
|
|
1342
|
+
return generate_http_error_flask(400, ValueError.__name__, 'Account must be set.')
|
|
1343
|
+
|
|
1335
1344
|
result = get_ssh_challenge_token(account, appid, ip, vo=vo)
|
|
1336
1345
|
|
|
1337
1346
|
if not result:
|
|
@@ -1452,6 +1461,9 @@ class SAML(ErrorHandlingMethodView):
|
|
|
1452
1461
|
appid = request.headers.get('X-Rucio-AppID', default='unknown')
|
|
1453
1462
|
ip = request.headers.get('X-Forwarded-For', default=request.remote_addr)
|
|
1454
1463
|
|
|
1464
|
+
if not account:
|
|
1465
|
+
return generate_http_error_flask(400, ValueError.__name__, 'Account must be set.')
|
|
1466
|
+
|
|
1455
1467
|
if saml_nameid:
|
|
1456
1468
|
try:
|
|
1457
1469
|
result = get_auth_token_saml(account, saml_nameid, appid, ip, vo=vo)
|
|
@@ -1592,6 +1604,9 @@ class Validate(ErrorHandlingMethodView):
|
|
|
1592
1604
|
|
|
1593
1605
|
token = request.headers.get('X-Rucio-Auth-Token', default=None)
|
|
1594
1606
|
|
|
1607
|
+
if not token:
|
|
1608
|
+
return generate_http_error_flask(400, ValueError.__name__, 'Token must be set.')
|
|
1609
|
+
|
|
1595
1610
|
result = validate_auth_token(token)
|
|
1596
1611
|
if not result:
|
|
1597
1612
|
return generate_http_error_flask(
|
|
@@ -148,6 +148,9 @@ def request_auth_env() -> Optional['ResponseReturnValue']:
|
|
|
148
148
|
|
|
149
149
|
auth_token = flask.request.headers.get('X-Rucio-Auth-Token', default=None)
|
|
150
150
|
|
|
151
|
+
if not auth_token:
|
|
152
|
+
return generate_http_error_flask(400, ValueError.__name__, 'Token must be set.')
|
|
153
|
+
|
|
151
154
|
try:
|
|
152
155
|
auth = validate_auth_token(auth_token)
|
|
153
156
|
except CannotAuthenticate:
|
|
@@ -46,9 +46,9 @@ class Config(ErrorHandlingMethodView):
|
|
|
46
46
|
description: Not acceptable
|
|
47
47
|
"""
|
|
48
48
|
res = {}
|
|
49
|
-
for section in config.sections(issuer=request.environ
|
|
49
|
+
for section in config.sections(issuer=request.environ['issuer'], vo=request.environ['vo']):
|
|
50
50
|
res[section] = {}
|
|
51
|
-
for item in config.items(section, issuer=request.environ
|
|
51
|
+
for item in config.items(section, issuer=request.environ['issuer'], vo=request.environ['vo']):
|
|
52
52
|
res[section][item[0]] = item[1]
|
|
53
53
|
|
|
54
54
|
return jsonify(res), 200
|
|
@@ -87,7 +87,7 @@ class Config(ErrorHandlingMethodView):
|
|
|
87
87
|
return generate_http_error_flask(400, ValueError.__name__, '')
|
|
88
88
|
for option, value in section_config.items():
|
|
89
89
|
try:
|
|
90
|
-
config.set(section=section, option=option, value=value, issuer=request.environ
|
|
90
|
+
config.set(section=section, option=option, value=value, issuer=request.environ['issuer'], vo=request.environ['vo'])
|
|
91
91
|
except ConfigurationError:
|
|
92
92
|
return generate_http_error_flask(400, 'ConfigurationError', f"Could not set value '{value}' for section '{section}' option '{option}'")
|
|
93
93
|
return 'Created', 201
|
|
@@ -137,7 +137,7 @@ class Section(ErrorHandlingMethodView):
|
|
|
137
137
|
description: Not acceptable
|
|
138
138
|
"""
|
|
139
139
|
res = {}
|
|
140
|
-
for item in config.items(section, issuer=request.environ
|
|
140
|
+
for item in config.items(section, issuer=request.environ['issuer'], vo=request.environ['vo']):
|
|
141
141
|
res[item[0]] = item[1]
|
|
142
142
|
|
|
143
143
|
if res == {}:
|
|
@@ -190,7 +190,7 @@ class OptionGetDel(ErrorHandlingMethodView):
|
|
|
190
190
|
description: Not acceptable
|
|
191
191
|
"""
|
|
192
192
|
try:
|
|
193
|
-
result = config.get(section=section, option=option, issuer=request.environ
|
|
193
|
+
result = config.get(section=section, option=option, issuer=request.environ['issuer'], vo=request.environ['vo'])
|
|
194
194
|
return jsonify(result), 200
|
|
195
195
|
except AccessDenied as error:
|
|
196
196
|
return generate_http_error_flask(401, error, f"Access to '{section}' option '{option}' denied")
|
|
@@ -223,7 +223,7 @@ class OptionGetDel(ErrorHandlingMethodView):
|
|
|
223
223
|
401:
|
|
224
224
|
description: Invalid Auth Token
|
|
225
225
|
"""
|
|
226
|
-
config.remove_option(section=section, option=option, issuer=request.environ
|
|
226
|
+
config.remove_option(section=section, option=option, issuer=request.environ['issuer'], vo=request.environ['vo'])
|
|
227
227
|
return '', 200
|
|
228
228
|
|
|
229
229
|
|
|
@@ -275,7 +275,7 @@ class OptionSet(ErrorHandlingMethodView):
|
|
|
275
275
|
enum: ['Could not set value {} for section {} option {}']
|
|
276
276
|
"""
|
|
277
277
|
try:
|
|
278
|
-
config.set(section=section, option=option, value=value, issuer=request.environ
|
|
278
|
+
config.set(section=section, option=option, value=value, issuer=request.environ['issuer'], vo=request.environ['vo'])
|
|
279
279
|
return 'Created', 201
|
|
280
280
|
except ConfigurationError as error:
|
|
281
281
|
return generate_http_error_flask(500, error, f"Could not set value '{value}' for section '{section}' option '{option}'")
|