rucio-clients 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-clients might be problematic. Click here for more details.

Files changed (88) hide show
  1. rucio/__init__.py +18 -0
  2. rucio/alembicrevision.py +16 -0
  3. rucio/client/__init__.py +16 -0
  4. rucio/client/accountclient.py +413 -0
  5. rucio/client/accountlimitclient.py +155 -0
  6. rucio/client/baseclient.py +929 -0
  7. rucio/client/client.py +77 -0
  8. rucio/client/configclient.py +113 -0
  9. rucio/client/credentialclient.py +54 -0
  10. rucio/client/didclient.py +691 -0
  11. rucio/client/diracclient.py +48 -0
  12. rucio/client/downloadclient.py +1674 -0
  13. rucio/client/exportclient.py +44 -0
  14. rucio/client/fileclient.py +51 -0
  15. rucio/client/importclient.py +42 -0
  16. rucio/client/lifetimeclient.py +74 -0
  17. rucio/client/lockclient.py +99 -0
  18. rucio/client/metaclient.py +137 -0
  19. rucio/client/pingclient.py +45 -0
  20. rucio/client/replicaclient.py +444 -0
  21. rucio/client/requestclient.py +109 -0
  22. rucio/client/rseclient.py +664 -0
  23. rucio/client/ruleclient.py +287 -0
  24. rucio/client/scopeclient.py +88 -0
  25. rucio/client/subscriptionclient.py +161 -0
  26. rucio/client/touchclient.py +78 -0
  27. rucio/client/uploadclient.py +871 -0
  28. rucio/common/__init__.py +14 -0
  29. rucio/common/cache.py +74 -0
  30. rucio/common/config.py +796 -0
  31. rucio/common/constants.py +92 -0
  32. rucio/common/constraints.py +18 -0
  33. rucio/common/didtype.py +187 -0
  34. rucio/common/exception.py +1092 -0
  35. rucio/common/extra.py +37 -0
  36. rucio/common/logging.py +404 -0
  37. rucio/common/pcache.py +1387 -0
  38. rucio/common/policy.py +84 -0
  39. rucio/common/schema/__init__.py +143 -0
  40. rucio/common/schema/atlas.py +411 -0
  41. rucio/common/schema/belleii.py +406 -0
  42. rucio/common/schema/cms.py +478 -0
  43. rucio/common/schema/domatpc.py +399 -0
  44. rucio/common/schema/escape.py +424 -0
  45. rucio/common/schema/generic.py +431 -0
  46. rucio/common/schema/generic_multi_vo.py +410 -0
  47. rucio/common/schema/icecube.py +404 -0
  48. rucio/common/schema/lsst.py +423 -0
  49. rucio/common/stomp_utils.py +160 -0
  50. rucio/common/stopwatch.py +56 -0
  51. rucio/common/test_rucio_server.py +148 -0
  52. rucio/common/types.py +158 -0
  53. rucio/common/utils.py +1946 -0
  54. rucio/rse/__init__.py +97 -0
  55. rucio/rse/protocols/__init__.py +14 -0
  56. rucio/rse/protocols/cache.py +123 -0
  57. rucio/rse/protocols/dummy.py +112 -0
  58. rucio/rse/protocols/gfal.py +701 -0
  59. rucio/rse/protocols/globus.py +243 -0
  60. rucio/rse/protocols/gsiftp.py +93 -0
  61. rucio/rse/protocols/http_cache.py +83 -0
  62. rucio/rse/protocols/mock.py +124 -0
  63. rucio/rse/protocols/ngarc.py +210 -0
  64. rucio/rse/protocols/posix.py +251 -0
  65. rucio/rse/protocols/protocol.py +530 -0
  66. rucio/rse/protocols/rclone.py +365 -0
  67. rucio/rse/protocols/rfio.py +137 -0
  68. rucio/rse/protocols/srm.py +339 -0
  69. rucio/rse/protocols/ssh.py +414 -0
  70. rucio/rse/protocols/storm.py +207 -0
  71. rucio/rse/protocols/webdav.py +547 -0
  72. rucio/rse/protocols/xrootd.py +295 -0
  73. rucio/rse/rsemanager.py +752 -0
  74. rucio/vcsversion.py +11 -0
  75. rucio/version.py +46 -0
  76. rucio_clients-32.8.6.data/data/etc/rse-accounts.cfg.template +25 -0
  77. rucio_clients-32.8.6.data/data/etc/rucio.cfg.atlas.client.template +42 -0
  78. rucio_clients-32.8.6.data/data/etc/rucio.cfg.template +257 -0
  79. rucio_clients-32.8.6.data/data/requirements.txt +55 -0
  80. rucio_clients-32.8.6.data/data/rucio_client/merge_rucio_configs.py +147 -0
  81. rucio_clients-32.8.6.data/scripts/rucio +2540 -0
  82. rucio_clients-32.8.6.data/scripts/rucio-admin +2434 -0
  83. rucio_clients-32.8.6.dist-info/METADATA +50 -0
  84. rucio_clients-32.8.6.dist-info/RECORD +88 -0
  85. rucio_clients-32.8.6.dist-info/WHEEL +5 -0
  86. rucio_clients-32.8.6.dist-info/licenses/AUTHORS.rst +94 -0
  87. rucio_clients-32.8.6.dist-info/licenses/LICENSE +201 -0
  88. rucio_clients-32.8.6.dist-info/top_level.txt +1 -0
@@ -0,0 +1,287 @@
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 json import dumps, loads
17
+ from typing import Any, Optional, Union
18
+ from urllib.parse import quote_plus
19
+
20
+ from requests.status_codes import codes
21
+
22
+ from rucio.client.baseclient import BaseClient
23
+ from rucio.client.baseclient import choice
24
+ from rucio.common.utils import build_url
25
+
26
+
27
+ class RuleClient(BaseClient):
28
+
29
+ """RuleClient class for working with replication rules"""
30
+
31
+ RULE_BASEURL = 'rules'
32
+
33
+ def add_replication_rule(
34
+ self,
35
+ dids: list[str],
36
+ copies: int,
37
+ rse_expression: str,
38
+ priority: int = 3,
39
+ lifetime: Optional[int] = None,
40
+ grouping: str = 'DATASET',
41
+ notify: str = 'N',
42
+ source_replica_expression: Optional[str] = None,
43
+ activity: Optional[str] = None,
44
+ account: Optional[str] = None,
45
+ meta: Optional[str] = None,
46
+ ignore_availability: bool = False,
47
+ purge_replicas: bool = False,
48
+ ask_approval: bool = False,
49
+ asynchronous: bool = False,
50
+ locked: bool = False,
51
+ delay_injection=None,
52
+ comment=None,
53
+ weight=None,
54
+ ):
55
+ """
56
+ :param dids: The data identifier set.
57
+ :param copies: The number of replicas.
58
+ :param rse_expression: Boolean string expression to give the list of RSEs.
59
+ :param priority: Priority of the transfers.
60
+ :param lifetime: The lifetime of the replication rules (in seconds).
61
+ :param grouping: ALL - All files will be replicated to the same RSE.
62
+ DATASET - All files in the same dataset will be replicated to the same RSE.
63
+ NONE - Files will be completely spread over all allowed RSEs without any grouping considerations at all.
64
+ :param notify: Notification setting for the rule (Y, N, C).
65
+ :param source_replica_expression: RSE Expression for RSEs to be considered for source replicas.
66
+ :param activity: Transfer Activity to be passed to FTS.
67
+ :param account: The account owning the rule.
68
+ :param meta: Metadata, as dictionary.
69
+ :param ignore_availability: Option to ignore the availability of RSEs.
70
+ :param purge_replicas: When the rule gets deleted purge the associated replicas immediately.
71
+ :param ask_approval: Ask for approval of this replication rule.
72
+ :param asynchronous: Create rule asynchronously by judge-injector.
73
+ :param locked: If the rule is locked, it cannot be deleted.
74
+ :param delay_injection:
75
+ :param comment: Comment about the rule.
76
+ :param weight: If the weighting option of the replication rule is used, the choice of RSEs takes their weight into account.
77
+ """
78
+ path = self.RULE_BASEURL + '/'
79
+ url = build_url(choice(self.list_hosts), path=path)
80
+ # TODO remove the subscription_id from the client; It will only be used by the core;
81
+ data = dumps({'dids': dids, 'copies': copies, 'rse_expression': rse_expression,
82
+ 'weight': weight, 'lifetime': lifetime, 'grouping': grouping,
83
+ 'account': account, 'locked': locked, 'source_replica_expression': source_replica_expression,
84
+ 'activity': activity, 'notify': notify, 'purge_replicas': purge_replicas,
85
+ 'ignore_availability': ignore_availability, 'comment': comment, 'ask_approval': ask_approval,
86
+ 'asynchronous': asynchronous, 'delay_injection': delay_injection, 'priority': priority, 'meta': meta})
87
+ r = self._send_request(url, type_='POST', data=data)
88
+ if r.status_code == codes.created:
89
+ return loads(r.text)
90
+ exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
91
+ raise exc_cls(exc_msg)
92
+
93
+ def delete_replication_rule(
94
+ self, rule_id: str, purge_replicas: Optional[bool] = None
95
+ ):
96
+ """
97
+ Deletes a replication rule and all associated locks.
98
+
99
+ :param rule_id: The id of the rule to be deleted
100
+ :param purge_replicas: Immediately delete the replicas.
101
+ :raises: RuleNotFound, AccessDenied
102
+ """
103
+
104
+ path = self.RULE_BASEURL + '/' + rule_id
105
+ url = build_url(choice(self.list_hosts), path=path)
106
+
107
+ data = dumps({'purge_replicas': purge_replicas})
108
+
109
+ r = self._send_request(url, type_='DEL', data=data)
110
+
111
+ if r.status_code == codes.ok:
112
+ return True
113
+ exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
114
+ raise exc_cls(exc_msg)
115
+
116
+ def get_replication_rule(self, rule_id: str):
117
+ """
118
+ Get a replication rule.
119
+
120
+ :param rule_id: The id of the rule to be retrieved.
121
+ :raises: RuleNotFound
122
+ """
123
+ path = self.RULE_BASEURL + '/' + rule_id
124
+ url = build_url(choice(self.list_hosts), path=path)
125
+ r = self._send_request(url, type_='GET')
126
+ if r.status_code == codes.ok:
127
+ return next(self._load_json_data(r))
128
+ else:
129
+ exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
130
+ raise exc_cls(exc_msg)
131
+
132
+ def update_replication_rule(self, rule_id: str, options: dict[str, Any]):
133
+ """
134
+ :param rule_id: The id of the rule to be retrieved.
135
+ :param options: Options dictionary.
136
+ :raises: RuleNotFound
137
+ """
138
+ path = self.RULE_BASEURL + '/' + rule_id
139
+ url = build_url(choice(self.list_hosts), path=path)
140
+ data = dumps({'options': options})
141
+ r = self._send_request(url, type_='PUT', data=data)
142
+ if r.status_code == codes.ok:
143
+ return True
144
+ exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
145
+ raise exc_cls(exc_msg)
146
+
147
+ def reduce_replication_rule(
148
+ self, rule_id: str, copies: int, exclude_expression=None
149
+ ):
150
+ """
151
+ :param rule_id: Rule to be reduced.
152
+ :param copies: Number of copies of the new rule.
153
+ :param exclude_expression: RSE Expression of RSEs to exclude.
154
+ :raises: RuleReplaceFailed, RuleNotFound
155
+ """
156
+
157
+ path = self.RULE_BASEURL + '/' + rule_id + '/reduce'
158
+ url = build_url(choice(self.list_hosts), path=path)
159
+ data = dumps({'copies': copies, 'exclude_expression': exclude_expression})
160
+ r = self._send_request(url, type_='POST', data=data)
161
+ if r.status_code == codes.ok:
162
+ return loads(r.text)
163
+ exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
164
+ raise exc_cls(exc_msg)
165
+
166
+ def move_replication_rule(
167
+ self, rule_id: str, rse_expression: str, override
168
+ ):
169
+ """
170
+ Move a replication rule to another RSE and, once done, delete the original one.
171
+
172
+ :param rule_id: Rule to be moved.
173
+ :param rse_expression: RSE expression of the new rule.
174
+ :param override: Configurations to update for the new rule.
175
+ :raises: RuleNotFound, RuleReplaceFailed
176
+ """
177
+
178
+ path = self.RULE_BASEURL + '/' + rule_id + '/move'
179
+ url = build_url(choice(self.list_hosts), path=path)
180
+ data = dumps({
181
+ 'rule_id': rule_id,
182
+ 'rse_expression': rse_expression,
183
+ 'override': override,
184
+ })
185
+ r = self._send_request(url, type_='POST', data=data)
186
+ if r.status_code == codes.created:
187
+ return loads(r.text)
188
+ exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
189
+ raise exc_cls(exc_msg)
190
+
191
+ def approve_replication_rule(self, rule_id: str):
192
+ """
193
+ :param rule_id: Rule to be approved.
194
+ :raises: RuleNotFound
195
+ """
196
+
197
+ path = self.RULE_BASEURL + '/' + rule_id
198
+ url = build_url(choice(self.list_hosts), path=path)
199
+ data = dumps({'options': {'approve': True}})
200
+ r = self._send_request(url, type_='PUT', data=data)
201
+ if r.status_code == codes.ok:
202
+ return True
203
+ exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
204
+ raise exc_cls(exc_msg)
205
+
206
+ def deny_replication_rule(self, rule_id: str, reason: Optional[str] = None):
207
+ """
208
+ :param rule_id: Rule to be denied.
209
+ :param reason: Reason for denying the rule.
210
+ :raises: RuleNotFound
211
+ """
212
+
213
+ path = self.RULE_BASEURL + '/' + rule_id
214
+ url = build_url(choice(self.list_hosts), path=path)
215
+ options: dict[str, Union[bool, str]] = {'approve': False}
216
+ if reason:
217
+ options['comment'] = reason
218
+ data = dumps({'options': options})
219
+ r = self._send_request(url, type_='PUT', data=data)
220
+ if r.status_code == codes.ok:
221
+ return True
222
+ exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
223
+ raise exc_cls(exc_msg)
224
+
225
+ def list_replication_rule_full_history(
226
+ self, scope: Union[str, bytes], name: Union[str, bytes]
227
+ ):
228
+ """
229
+ List the rule history of a DID.
230
+
231
+ :param scope: The scope of the DID.
232
+ :param name: The name of the DID.
233
+ """
234
+ path = '/'.join([self.RULE_BASEURL, quote_plus(scope), quote_plus(name), 'history'])
235
+ url = build_url(choice(self.list_hosts), path=path)
236
+ r = self._send_request(url, type_='GET')
237
+ if r.status_code == codes.ok:
238
+ return self._load_json_data(r)
239
+ exc_cls, exc_msg = self._get_exception(r.headers, r.status_code)
240
+ raise exc_cls(exc_msg)
241
+
242
+ def examine_replication_rule(self, rule_id: str):
243
+ """
244
+ Examine a replication rule for errors during transfer.
245
+
246
+ :param rule_id: Rule to be denied.
247
+ :raises: RuleNotFound
248
+ """
249
+ path = self.RULE_BASEURL + '/' + rule_id + '/analysis'
250
+ url = build_url(choice(self.list_hosts), path=path)
251
+ r = self._send_request(url, type_='GET')
252
+ if r.status_code == codes.ok:
253
+ return next(self._load_json_data(r))
254
+ exc_cls, exc_msg = self._get_exception(r.headers, r.status_code)
255
+ raise exc_cls(exc_msg)
256
+
257
+ def list_replica_locks(self, rule_id: str):
258
+ """
259
+ List details of all replica locks for a rule.
260
+
261
+ :param rule_id: Rule to be denied.
262
+ :raises: RuleNotFound
263
+ """
264
+ path = self.RULE_BASEURL + '/' + rule_id + '/locks'
265
+ url = build_url(choice(self.list_hosts), path=path)
266
+ r = self._send_request(url, type_='GET')
267
+ if r.status_code == codes.ok:
268
+ return self._load_json_data(r)
269
+ exc_cls, exc_msg = self._get_exception(r.headers, r.status_code)
270
+ raise exc_cls(exc_msg)
271
+
272
+ def list_replication_rules(self, filters=None):
273
+ """
274
+ List all replication rules which match a filter
275
+ :param filters: dictionary of attributes by which the rules should be filtered
276
+
277
+ :returns: True if successful, otherwise false.
278
+ """
279
+ filters = filters or {}
280
+ path = self.RULE_BASEURL + '/'
281
+ url = build_url(choice(self.list_hosts), path=path)
282
+ r = self._send_request(url, type_='GET', params=filters)
283
+ if r.status_code == codes.ok:
284
+ return self._load_json_data(r)
285
+ else:
286
+ exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
287
+ raise exc_cls(exc_msg)
@@ -0,0 +1,88 @@
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 json import loads
17
+ from urllib.parse import quote_plus
18
+
19
+ from requests.status_codes import codes
20
+
21
+ from rucio.client.baseclient import BaseClient
22
+ from rucio.client.baseclient import choice
23
+ from rucio.common.utils import build_url
24
+
25
+
26
+ class ScopeClient(BaseClient):
27
+
28
+ """Scope client class for working with rucio scopes"""
29
+
30
+ SCOPE_BASEURL = 'accounts'
31
+
32
+ def add_scope(self, account, scope):
33
+ """
34
+ Sends the request to add a new scope.
35
+
36
+ :param account: the name of the account to add the scope to.
37
+ :param scope: the name of the new scope.
38
+ :return: True if scope was created successfully.
39
+ :raises Duplicate: if scope already exists.
40
+ :raises AccountNotFound: if account doesn't exist.
41
+ """
42
+
43
+ path = '/'.join([self.SCOPE_BASEURL, account, 'scopes', quote_plus(scope)])
44
+ url = build_url(choice(self.list_hosts), path=path)
45
+ r = self._send_request(url, type_='POST')
46
+ if r.status_code == codes.created:
47
+ return True
48
+ else:
49
+ exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
50
+ raise exc_cls(exc_msg)
51
+
52
+ def list_scopes(self):
53
+ """
54
+ Sends the request to list all scopes.
55
+
56
+ :return: a list containing the names of all scopes.
57
+ """
58
+
59
+ path = '/'.join(['scopes/'])
60
+ url = build_url(choice(self.list_hosts), path=path)
61
+ r = self._send_request(url)
62
+ if r.status_code == codes.ok:
63
+ scopes = loads(r.text)
64
+ return scopes
65
+ else:
66
+ exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
67
+ raise exc_cls(exc_msg)
68
+
69
+ def list_scopes_for_account(self, account):
70
+ """
71
+ Sends the request to list all scopes for a rucio account.
72
+
73
+ :param account: the rucio account to list scopes for.
74
+ :return: a list containing the names of all scopes for a rucio account.
75
+ :raises AccountNotFound: if account doesn't exist.
76
+ :raises ScopeNotFound: if no scopes exist for account.
77
+ """
78
+
79
+ path = '/'.join([self.SCOPE_BASEURL, account, 'scopes/'])
80
+ url = build_url(choice(self.list_hosts), path=path)
81
+
82
+ r = self._send_request(url)
83
+ if r.status_code == codes.ok:
84
+ scopes = loads(r.text)
85
+ return scopes
86
+ else:
87
+ exc_cls, exc_msg = self._get_exception(headers=r.headers, status_code=r.status_code, data=r.content)
88
+ raise exc_cls(exc_msg)
@@ -0,0 +1,161 @@
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 json import dumps
17
+
18
+ from requests.status_codes import codes
19
+
20
+ from rucio.client.baseclient import BaseClient
21
+ from rucio.client.baseclient import choice
22
+ from rucio.common.utils import build_url
23
+
24
+
25
+ class SubscriptionClient(BaseClient):
26
+
27
+ """SubscriptionClient class for working with subscriptions"""
28
+
29
+ SUB_BASEURL = 'subscriptions'
30
+
31
+ def add_subscription(self, name, account, filter_, replication_rules, comments, lifetime, retroactive, dry_run, priority=3):
32
+ """
33
+ Adds a new subscription which will be verified against every new added file and dataset
34
+
35
+ :param name: Name of the subscription
36
+ :type: String
37
+ :param account: Account identifier
38
+ :type account: String
39
+ :param filter_: Dictionary of attributes by which the input data should be filtered
40
+ **Example**: ``{'dsn': 'data11_hi*.express_express.*,data11_hi*physics_MinBiasOverlay*', 'account': 'tzero'}``
41
+ :type filter_: Dict
42
+ :param replication_rules: Replication rules to be set : Dictionary with keys copies, rse_expression, weight, rse_expression
43
+ :type replication_rules: Dict
44
+ :param comments: Comments for the subscription
45
+ :type comments: String
46
+ :param lifetime: Subscription's lifetime (days); False if subscription has no lifetime
47
+ :type lifetime: Integer or False
48
+ :param retroactive: Flag to know if the subscription should be applied on previous data
49
+ :type retroactive: Boolean
50
+ :param dry_run: Just print the subscriptions actions without actually executing them (Useful if retroactive flag is set)
51
+ :type dry_run: Boolean
52
+ :param priority: The priority of the subscription (3 by default)
53
+ :type priority: Integer
54
+ """
55
+ path = self.SUB_BASEURL + '/' + account + '/' + name
56
+ url = build_url(choice(self.list_hosts), path=path)
57
+ if retroactive:
58
+ raise NotImplementedError('Retroactive mode is not implemented')
59
+ if filter_ and not isinstance(filter_, dict):
60
+ raise TypeError('filter should be a dict')
61
+ if replication_rules and not isinstance(replication_rules, list):
62
+ raise TypeError('replication_rules should be a list')
63
+ data = dumps({'options': {'filter': filter_, 'replication_rules': replication_rules, 'comments': comments,
64
+ 'lifetime': lifetime, 'retroactive': retroactive, 'dry_run': dry_run, 'priority': priority}})
65
+ result = self._send_request(url, type_='POST', data=data)
66
+ if result.status_code == codes.created: # pylint: disable=no-member
67
+ return result.text
68
+ else:
69
+ exc_cls, exc_msg = self._get_exception(headers=result.headers, status_code=result.status_code, data=result.content)
70
+ raise exc_cls(exc_msg)
71
+
72
+ def list_subscriptions(self, name=None, account=None):
73
+ """
74
+ Returns a dictionary with the subscription information :
75
+ Examples: ``{'status': 'INACTIVE/ACTIVE/BROKEN', 'last_modified_date': ...}``
76
+
77
+ :param name: Name of the subscription
78
+ :type: String
79
+ :param account: Account identifier
80
+ :type account: String
81
+ :returns: Dictionary containing subscription parameter
82
+ :rtype: Dict
83
+ :raises: exception.NotFound if subscription is not found
84
+ """
85
+ path = self.SUB_BASEURL
86
+ if account:
87
+ path += '/%s' % (account)
88
+ if name:
89
+ path += '/%s' % (name)
90
+ elif name:
91
+ path += '/Name/%s' % (name)
92
+ else:
93
+ path += '/'
94
+ url = build_url(choice(self.list_hosts), path=path)
95
+ result = self._send_request(url, type_='GET')
96
+ if result.status_code == codes.ok: # pylint: disable=no-member
97
+ return self._load_json_data(result)
98
+ else:
99
+ exc_cls, exc_msg = self._get_exception(headers=result.headers, status_code=result.status_code, data=result.content)
100
+ raise exc_cls(exc_msg)
101
+
102
+ def update_subscription(self, name, account=None, filter_=None, replication_rules=None, comments=None, lifetime=None, retroactive=None, dry_run=None, priority=None):
103
+ """
104
+ Updates a subscription
105
+
106
+ :param name: Name of the subscription
107
+ :type: String
108
+ :param account: Account identifier
109
+ :type account: String
110
+ :param filter_: Dictionary of attributes by which the input data should be filtered
111
+ **Example**: ``{'dsn': 'data11_hi*.express_express.*,data11_hi*physics_MinBiasOverlay*', 'account': 'tzero'}``
112
+ :type filter_: Dict
113
+ :param replication_rules: Replication rules to be set : Dictionary with keys copies, rse_expression, weight, rse_expression
114
+ :type replication_rules: Dict
115
+ :param comments: Comments for the subscription
116
+ :type comments: String
117
+ :param lifetime: Subscription's lifetime (days); False if subscription has no lifetime
118
+ :type lifetime: Integer or False
119
+ :param retroactive: Flag to know if the subscription should be applied on previous data
120
+ :type retroactive: Boolean
121
+ :param dry_run: Just print the subscriptions actions without actually executing them (Useful if retroactive flag is set)
122
+ :type dry_run: Boolean
123
+ :param priority: The priority of the subscription
124
+ :type priority: Integer
125
+ :raises: exception.NotFound if subscription is not found
126
+ """
127
+ if not account:
128
+ account = self.account
129
+ if retroactive:
130
+ raise NotImplementedError('Retroactive mode is not implemented')
131
+ path = self.SUB_BASEURL + '/' + account + '/' + name
132
+ url = build_url(choice(self.list_hosts), path=path)
133
+ if filter_ and not isinstance(filter_, dict):
134
+ raise TypeError('filter should be a dict')
135
+ if replication_rules and not isinstance(replication_rules, list):
136
+ raise TypeError('replication_rules should be a list')
137
+ data = dumps({'options': {'filter': filter_, 'replication_rules': replication_rules, 'comments': comments,
138
+ 'lifetime': lifetime, 'retroactive': retroactive, 'dry_run': dry_run, 'priority': priority}})
139
+ result = self._send_request(url, type_='PUT', data=data)
140
+ if result.status_code == codes.created: # pylint: disable=no-member
141
+ return True
142
+ else:
143
+ exc_cls, exc_msg = self._get_exception(headers=result.headers, status_code=result.status_code, data=result.content)
144
+ raise exc_cls(exc_msg)
145
+
146
+ def list_subscription_rules(self, account, name):
147
+ """
148
+ List the associated rules of a subscription.
149
+
150
+ :param account: Account of the subscription.
151
+ :param name: Name of the subscription.
152
+ """
153
+
154
+ path = '/'.join([self.SUB_BASEURL, account, name, 'Rules'])
155
+ url = build_url(choice(self.list_hosts), path=path)
156
+ result = self._send_request(url, type_='GET')
157
+ if result.status_code == codes.ok: # pylint: disable=no-member
158
+ return self._load_json_data(result)
159
+ else:
160
+ exc_cls, exc_msg = self._get_exception(headers=result.headers, status_code=result.status_code, data=result.content)
161
+ raise exc_cls(exc_msg)
@@ -0,0 +1,78 @@
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 json import dumps
17
+
18
+ from requests import post
19
+
20
+ from rucio.client.baseclient import BaseClient
21
+ from rucio.client.baseclient import choice
22
+ from rucio.common.exception import RucioException, UnsupportedDIDType
23
+
24
+
25
+ class TouchClient(BaseClient):
26
+
27
+ """
28
+ Touch client class to send a trace that can be used to
29
+ update accessed_at for file or dataset DIDs
30
+ """
31
+
32
+ DIDS_BASEURL = 'dids'
33
+ TRACES_BASEURL = 'traces'
34
+
35
+ def touch(self, scope, name, rse=None):
36
+ """
37
+ Sends a touch trace for a given file or dataset.
38
+
39
+ :param scope: the scope of the file/dataset to update.
40
+ :param name: the name of file/dataset to update.
41
+ :param rse: optional parameter if a specific replica should be touched.
42
+ :raises DataIdentifierNotFound: if given dids does not exist.
43
+ :raises RSENotFound: if rse is not None and given rse does not exist.
44
+ :raises UnsupportedDIDType: if type of the given DID is not FILE or DATASET.
45
+ :raises RucioException: if trace could not be sent successfully.
46
+ """
47
+
48
+ trace = {}
49
+
50
+ trace['eventType'] = 'touch'
51
+ trace['clientState'] = 'DONE'
52
+ trace['account'] = self.account
53
+ if self.vo != 'def':
54
+ trace['vo'] = self.vo
55
+
56
+ if rse:
57
+ self.get_rse(rse) # pylint: disable=no-member
58
+
59
+ trace['localSite'] = trace['remoteSite'] = rse
60
+
61
+ info = self.get_did(scope, name) # pylint: disable=no-member
62
+
63
+ if info['type'] == 'CONTAINER':
64
+ raise UnsupportedDIDType("%s:%s is a container." % (scope, name))
65
+
66
+ if info['type'] == 'FILE':
67
+ trace['scope'] = scope
68
+ trace['filename'] = name
69
+ elif info['type'] == 'DATASET':
70
+ trace['datasetScope'] = scope
71
+ trace['dataset'] = name
72
+
73
+ url = '%s/%s/' % (choice(self.list_hosts), self.TRACES_BASEURL)
74
+
75
+ try:
76
+ post(url, verify=False, data=dumps(trace))
77
+ except Exception as error:
78
+ raise RucioException("Could not send trace. " + str(error))