rucio-clients 37.0.0rc1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of rucio-clients might be problematic. Click here for more details.

Files changed (104) hide show
  1. rucio/__init__.py +17 -0
  2. rucio/alembicrevision.py +15 -0
  3. rucio/cli/__init__.py +14 -0
  4. rucio/cli/account.py +216 -0
  5. rucio/cli/bin_legacy/__init__.py +13 -0
  6. rucio/cli/bin_legacy/rucio.py +2825 -0
  7. rucio/cli/bin_legacy/rucio_admin.py +2500 -0
  8. rucio/cli/command.py +272 -0
  9. rucio/cli/config.py +72 -0
  10. rucio/cli/did.py +191 -0
  11. rucio/cli/download.py +128 -0
  12. rucio/cli/lifetime_exception.py +33 -0
  13. rucio/cli/replica.py +162 -0
  14. rucio/cli/rse.py +293 -0
  15. rucio/cli/rule.py +158 -0
  16. rucio/cli/scope.py +40 -0
  17. rucio/cli/subscription.py +73 -0
  18. rucio/cli/upload.py +60 -0
  19. rucio/cli/utils.py +226 -0
  20. rucio/client/__init__.py +15 -0
  21. rucio/client/accountclient.py +432 -0
  22. rucio/client/accountlimitclient.py +183 -0
  23. rucio/client/baseclient.py +983 -0
  24. rucio/client/client.py +120 -0
  25. rucio/client/configclient.py +126 -0
  26. rucio/client/credentialclient.py +59 -0
  27. rucio/client/didclient.py +868 -0
  28. rucio/client/diracclient.py +56 -0
  29. rucio/client/downloadclient.py +1783 -0
  30. rucio/client/exportclient.py +44 -0
  31. rucio/client/fileclient.py +50 -0
  32. rucio/client/importclient.py +42 -0
  33. rucio/client/lifetimeclient.py +90 -0
  34. rucio/client/lockclient.py +109 -0
  35. rucio/client/metaconventionsclient.py +140 -0
  36. rucio/client/pingclient.py +44 -0
  37. rucio/client/replicaclient.py +452 -0
  38. rucio/client/requestclient.py +125 -0
  39. rucio/client/richclient.py +317 -0
  40. rucio/client/rseclient.py +746 -0
  41. rucio/client/ruleclient.py +294 -0
  42. rucio/client/scopeclient.py +90 -0
  43. rucio/client/subscriptionclient.py +173 -0
  44. rucio/client/touchclient.py +82 -0
  45. rucio/client/uploadclient.py +969 -0
  46. rucio/common/__init__.py +13 -0
  47. rucio/common/bittorrent.py +234 -0
  48. rucio/common/cache.py +111 -0
  49. rucio/common/checksum.py +168 -0
  50. rucio/common/client.py +122 -0
  51. rucio/common/config.py +788 -0
  52. rucio/common/constants.py +217 -0
  53. rucio/common/constraints.py +17 -0
  54. rucio/common/didtype.py +237 -0
  55. rucio/common/exception.py +1208 -0
  56. rucio/common/extra.py +31 -0
  57. rucio/common/logging.py +420 -0
  58. rucio/common/pcache.py +1409 -0
  59. rucio/common/plugins.py +185 -0
  60. rucio/common/policy.py +93 -0
  61. rucio/common/schema/__init__.py +200 -0
  62. rucio/common/schema/generic.py +416 -0
  63. rucio/common/schema/generic_multi_vo.py +395 -0
  64. rucio/common/stomp_utils.py +423 -0
  65. rucio/common/stopwatch.py +55 -0
  66. rucio/common/test_rucio_server.py +154 -0
  67. rucio/common/types.py +483 -0
  68. rucio/common/utils.py +1688 -0
  69. rucio/rse/__init__.py +96 -0
  70. rucio/rse/protocols/__init__.py +13 -0
  71. rucio/rse/protocols/bittorrent.py +194 -0
  72. rucio/rse/protocols/cache.py +111 -0
  73. rucio/rse/protocols/dummy.py +100 -0
  74. rucio/rse/protocols/gfal.py +708 -0
  75. rucio/rse/protocols/globus.py +243 -0
  76. rucio/rse/protocols/http_cache.py +82 -0
  77. rucio/rse/protocols/mock.py +123 -0
  78. rucio/rse/protocols/ngarc.py +209 -0
  79. rucio/rse/protocols/posix.py +250 -0
  80. rucio/rse/protocols/protocol.py +361 -0
  81. rucio/rse/protocols/rclone.py +365 -0
  82. rucio/rse/protocols/rfio.py +145 -0
  83. rucio/rse/protocols/srm.py +338 -0
  84. rucio/rse/protocols/ssh.py +414 -0
  85. rucio/rse/protocols/storm.py +195 -0
  86. rucio/rse/protocols/webdav.py +594 -0
  87. rucio/rse/protocols/xrootd.py +302 -0
  88. rucio/rse/rsemanager.py +881 -0
  89. rucio/rse/translation.py +260 -0
  90. rucio/vcsversion.py +11 -0
  91. rucio/version.py +45 -0
  92. rucio_clients-37.0.0rc1.data/data/etc/rse-accounts.cfg.template +25 -0
  93. rucio_clients-37.0.0rc1.data/data/etc/rucio.cfg.atlas.client.template +43 -0
  94. rucio_clients-37.0.0rc1.data/data/etc/rucio.cfg.template +241 -0
  95. rucio_clients-37.0.0rc1.data/data/requirements.client.txt +19 -0
  96. rucio_clients-37.0.0rc1.data/data/rucio_client/merge_rucio_configs.py +144 -0
  97. rucio_clients-37.0.0rc1.data/scripts/rucio +133 -0
  98. rucio_clients-37.0.0rc1.data/scripts/rucio-admin +97 -0
  99. rucio_clients-37.0.0rc1.dist-info/METADATA +54 -0
  100. rucio_clients-37.0.0rc1.dist-info/RECORD +104 -0
  101. rucio_clients-37.0.0rc1.dist-info/WHEEL +5 -0
  102. rucio_clients-37.0.0rc1.dist-info/licenses/AUTHORS.rst +100 -0
  103. rucio_clients-37.0.0rc1.dist-info/licenses/LICENSE +201 -0
  104. rucio_clients-37.0.0rc1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,2500 @@
1
+ #!/usr/bin/env python
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 argparse
17
+ import datetime
18
+ import json
19
+ import math
20
+ import os
21
+ import signal
22
+ import sys
23
+ import time
24
+ from textwrap import dedent
25
+
26
+ from rich.console import Console
27
+ from rich.padding import Padding
28
+ from rich.status import Status
29
+ from rich.text import Text
30
+ from rich.theme import Theme
31
+ from rich.traceback import install
32
+ from rich.tree import Tree
33
+ from tabulate import tabulate
34
+
35
+ from rucio import version
36
+ from rucio.cli.utils import exception_handler, get_client, setup_gfal2_logger, signal_handler
37
+ from rucio.client.richclient import MAX_TRACEBACK_WIDTH, MIN_CONSOLE_WIDTH, CLITheme, generate_table, get_cli_config, get_pager, print_output, setup_rich_logger
38
+ from rucio.common.constants import RseAttr
39
+ from rucio.common.exception import (
40
+ ReplicaNotFound,
41
+ RSEOperationNotSupported,
42
+ )
43
+ from rucio.common.extra import import_extras
44
+ from rucio.common.utils import StoreAndDeprecateWarningAction, chunks, clean_pfns, construct_non_deterministic_pfn, extract_scope, get_bytes_value_from_string, parse_response, render_json, setup_logger, sizefmt
45
+ from rucio.rse import rsemanager as rsemgr
46
+
47
+ EXTRA_MODULES = import_extras(['argcomplete'])
48
+
49
+ if EXTRA_MODULES['argcomplete']:
50
+ import argcomplete # pylint: disable=E0401
51
+
52
+ possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
53
+ os.pardir, os.pardir))
54
+ if os.path.exists(os.path.join(possible_topdir, 'lib/rucio', '__init__.py')):
55
+ sys.path.insert(0, possible_topdir)
56
+
57
+ SUCCESS = 0
58
+ FAILURE = 1
59
+ DEFAULT_PORT = 443
60
+
61
+
62
+ tablefmt = 'psql'
63
+ cli_config = get_cli_config()
64
+
65
+
66
+ def get_scope(did, client):
67
+ try:
68
+ scope, name = extract_scope(did)
69
+ return scope, name
70
+ except TypeError:
71
+ scopes = client.list_scopes()
72
+ scope, name = extract_scope(did, scopes)
73
+ return scope, name
74
+
75
+
76
+ @exception_handler
77
+ def add_account(args, client, logger, console, spinner):
78
+ """
79
+ %(prog)s add [options] <field1=value1 field2=value2 ...>
80
+
81
+ Adds a new account. Specify metadata fields as arguments.
82
+
83
+ """
84
+ client.add_account(account=args.account, type_=args.account_type, email=args.email)
85
+ print('Added new account: %s' % args.account)
86
+ return SUCCESS
87
+
88
+
89
+ @exception_handler
90
+ def delete_account(args, client, logger, console, spinner):
91
+ """
92
+ %(prog)s disable [options] <field1=value1 field2=value2 ...>
93
+
94
+ Delete account.
95
+
96
+ """
97
+ client.delete_account(args.account)
98
+ print('Deleted account: %s' % args.account)
99
+ return SUCCESS
100
+
101
+
102
+ @exception_handler
103
+ def update_account(args, client, logger, console, spinner):
104
+ """
105
+ %(prog)s update [options] <field1=value1 field2=value2 ...>
106
+
107
+ Update an account.
108
+
109
+ """
110
+ client.update_account(account=args.account, key=args.key, value=args.value)
111
+ print('%s of account %s changed to %s' % (args.key, args.account, args.value))
112
+ return SUCCESS
113
+
114
+
115
+ @exception_handler
116
+ def ban_account(args, client, logger, console, spinner):
117
+ """
118
+ %(prog)s ban [options] <field1=value1 field2=value2 ...>
119
+
120
+ Ban an account.
121
+
122
+ """
123
+ client.update_account(account=args.account, key='status', value='SUSPENDED')
124
+ print('Account %s banned' % args.account)
125
+ return SUCCESS
126
+
127
+
128
+ @exception_handler
129
+ def unban_account(args, client, logger, console, spinner):
130
+ """
131
+ %(prog)s unban [options] <field1=value1 field2=value2 ...>
132
+
133
+ Unban a banned account.
134
+
135
+ """
136
+ client.update_account(account=args.account, key='status', value='ACTIVE')
137
+ print('Account %s unbanned' % args.account)
138
+ return SUCCESS
139
+
140
+
141
+ @exception_handler
142
+ def list_accounts(args, client, logger, console, spinner):
143
+ """
144
+ %(prog)s list [options] <field1=value1 field2=value2 ...>
145
+
146
+ List accounts.
147
+
148
+ """
149
+ filters = {}
150
+ if args.filters:
151
+ for key, value in [(_.split('=')[0], _.split('=')[1]) for _ in args.filters.split(',')]:
152
+ filters[key] = value
153
+ accounts = client.list_accounts(identity=args.identity, account_type=args.account_type, filters=filters)
154
+ if args.csv:
155
+ print(*(account['account'] for account in accounts), sep=',')
156
+ elif cli_config == 'rich':
157
+ table = generate_table([[account['account']] for account in accounts], headers=['ACCOUNT'], col_alignments=['left'])
158
+ spinner.stop()
159
+ print_output(table, console=console, no_pager=args.no_pager)
160
+ else:
161
+ for account in accounts:
162
+ print(account['account'])
163
+ return SUCCESS
164
+
165
+
166
+ @exception_handler
167
+ def info_account(args, client, logger, console, spinner):
168
+ """
169
+ %(prog)s show [options] <field1=value1 field2=value2 ...>
170
+
171
+ Show extended information of a given account
172
+
173
+ """
174
+ info = client.get_account(account=args.account)
175
+ if cli_config == 'rich':
176
+ keyword_style = {**CLITheme.ACCOUNT_STATUS, **CLITheme.ACCOUNT_TYPE}
177
+ table_data = [(k, Text(str(v), style=keyword_style.get(str(v), 'default'))) for k, v in sorted(info.items())]
178
+ table = generate_table(table_data, row_styles=['none'], col_alignments=['left', 'left'])
179
+ print_output(table, console=console, no_pager=args.no_pager)
180
+ else:
181
+ for k in info:
182
+ print(k.ljust(10) + ' : ' + str(info[k]))
183
+ return SUCCESS
184
+
185
+
186
+ @exception_handler
187
+ def list_identities(args, client, logger, console, spinner):
188
+ """
189
+ %(prog)s list-identities [options] <field1=value1 field2=value2 ...>
190
+
191
+ List all identities on an account.
192
+ """
193
+ table_data = []
194
+ identities = client.list_identities(account=args.account)
195
+ for identity in identities:
196
+ if cli_config == 'rich':
197
+ table_data.append([identity['identity'], identity['type']])
198
+ else:
199
+ print('Identity: %(identity)s,\ttype: %(type)s' % identity)
200
+ if cli_config == 'rich':
201
+ table = generate_table(table_data, headers=['IDENTITY', 'TYPE'], col_alignments=['left', 'left'])
202
+ print_output(table, console=console, no_pager=args.no_pager)
203
+ return SUCCESS
204
+
205
+
206
+ @exception_handler
207
+ def set_limits(args, client, logger, console, spinner):
208
+ """
209
+ %(prog)s set [options] <field1=value1 field2=value2 ...>
210
+
211
+ Set account limit for an account and rse.
212
+ """
213
+ locality = args.locality.lower()
214
+ byte_limit = None
215
+ limit_input = args.bytes.lower()
216
+
217
+ if limit_input == 'inf' or limit_input == 'infinity':
218
+ byte_limit = -1
219
+ else:
220
+ byte_limit = get_bytes_value_from_string(limit_input)
221
+ if not byte_limit:
222
+ try:
223
+ byte_limit = int(limit_input)
224
+ except ValueError:
225
+ logger.error('The limit could not be set. Either you misspelled infinity or your input could not be converted to integer or you used a wrong pattern. Please use a format like 10GB with B,KB,MB,GB,TB,PB as units (not case sensitive)')
226
+ return FAILURE
227
+
228
+ client.set_account_limit(account=args.account, rse=args.rse, bytes_=byte_limit, locality=locality)
229
+ print('Set account limit for account %s on RSE %s: %s' % (args.account, args.rse, sizefmt(byte_limit, True)))
230
+ return SUCCESS
231
+
232
+
233
+ @exception_handler
234
+ def get_limits(args, client, logger, console, spinner):
235
+ """
236
+ %(prog)s get-limits [options] <field1=value1 field2=value2 ...>
237
+
238
+ Grant an identity access to an account.
239
+
240
+ """
241
+ locality = args.locality.lower()
242
+ limits = client.get_account_limits(account=args.account, rse_expression=args.rse, locality=locality)
243
+ for rse in limits:
244
+ print('Quota on %s for %s : %s' % (rse, args.account, sizefmt(limits[rse], True)))
245
+ return SUCCESS
246
+
247
+
248
+ @exception_handler
249
+ def delete_limits(args, client, logger, console, spinner):
250
+ """
251
+ %(prog)s delete [options] <field1=value1 field2=value2 ...>
252
+
253
+ Delete account limit for an account and rse.
254
+ """
255
+ client.delete_account_limit(account=args.account, rse=args.rse, locality=args.locality)
256
+ print('Deleted account limit for account %s and RSE %s' % (args.account, args.rse))
257
+ return SUCCESS
258
+
259
+
260
+ @exception_handler
261
+ def identity_add(args, client, logger, console, spinner):
262
+ """
263
+ %(prog)s del [options] <field1=value1 field2=value2 ...>
264
+
265
+ Grant an identity access to an account.
266
+
267
+ """
268
+ if args.email == "":
269
+ logger.error('Error: --email argument can\'t be an empty string. Failed to grant an identity access to an account')
270
+ return FAILURE
271
+
272
+ if args.authtype == 'USERPASS' and not args.password:
273
+ logger.error('missing --password argument')
274
+ return FAILURE
275
+
276
+ client.add_identity(account=args.account, identity=args.identity, authtype=args.authtype, email=args.email, password=args.password)
277
+ print('Added new identity to account: %s-%s' % (args.identity, args.account))
278
+ return SUCCESS
279
+
280
+
281
+ @exception_handler
282
+ def identity_delete(args, client, logger, console, spinner):
283
+ """
284
+ %(prog)s delete [options] <field1=value1 field2=value2 ...>
285
+
286
+ Revoke an identity's access to an account.
287
+
288
+ """
289
+ client.del_identity(args.account, args.identity, authtype=args.authtype)
290
+ print('Deleted identity: %s' % args.identity)
291
+ return SUCCESS
292
+
293
+
294
+ @exception_handler
295
+ def add_rse(args, client, logger, console, spinner):
296
+ """
297
+ %(prog)s add [options] <field1=value1 field2=value2 ...>
298
+
299
+ Adds a new RSE. Specify metadata fields as arguments.
300
+
301
+ """
302
+ client.add_rse(args.rse, deterministic=not args.non_deterministic)
303
+ print('Added new %sdeterministic RSE: %s' % ('non-' if args.non_deterministic else '', args.rse))
304
+ return SUCCESS
305
+
306
+
307
+ @exception_handler
308
+ def disable_rse(args, client, logger, console, spinner):
309
+ """
310
+ %(prog)s del [options] <field1=value1 field2=value2 ...>
311
+
312
+ Disable RSE.
313
+
314
+ """
315
+ client.delete_rse(args.rse)
316
+ return SUCCESS
317
+
318
+
319
+ @exception_handler
320
+ def list_rses(args, client, logger, console, spinner):
321
+ """
322
+ %(prog)s list [options] <field1=value1 field2=value2 ...>
323
+
324
+ List RSEs.
325
+
326
+ """
327
+ if cli_config == 'rich':
328
+ spinner.update(status='Fetching RSEs')
329
+ spinner.start()
330
+
331
+ rses = client.list_rses()
332
+ if args.csv:
333
+ print(*(rse['rse'] for rse in rses), sep='\n')
334
+ elif cli_config == 'rich':
335
+ table_data = [[rse['rse']] for rse in sorted(rses, key=lambda elem: elem['rse'])]
336
+ table = generate_table(table_data, headers=['RSE'], col_alignments=['left'])
337
+ spinner.stop()
338
+ print_output(table, console=console, no_pager=args.no_pager)
339
+ else:
340
+ for rse in rses:
341
+ print(rse['rse'])
342
+ return SUCCESS
343
+
344
+
345
+ @exception_handler
346
+ def update_rse(args, client, logger, console, spinner):
347
+ """
348
+ %(prog)s update [options] <field1=value1 field2=value2 ...>
349
+
350
+ Update the settings of the RSE:
351
+ deterministic, rse_type, staging_are, volatile, qos_class,
352
+ availability_delete, availability_read, availability_write,
353
+ city, country_name, latitude, longitude, region_code, time_zone
354
+
355
+ Use '', 'None' or 'null' to wipe the value of following RSE settings:
356
+ qos_class
357
+ """
358
+ if args.value in ['true', 'True', 'TRUE', '1']:
359
+ args.value = True
360
+ if args.value in ['false', 'False', 'FALSE', '0']:
361
+ args.value = False
362
+ params = {args.param: args.value}
363
+ client.update_rse(args.rse, parameters=params)
364
+
365
+ if isinstance(args.value, bool):
366
+ args.value = str(args.value)
367
+
368
+ print('Updated RSE %s settings %s to %s' % (args.rse, args.param, args.value if args.value.lower() not in ['', 'none', 'null'] else '[WIPED]'))
369
+ return SUCCESS
370
+
371
+
372
+ @exception_handler
373
+ def info_rse(args, client, logger, console, spinner):
374
+ """
375
+ %(prog)s info [options] <field1=value1 field2=value2 ...>
376
+
377
+ Show extended information of a given RSE
378
+
379
+ """
380
+ if cli_config == 'rich':
381
+ spinner.update(status='Fetching RSE info')
382
+ spinner.start()
383
+
384
+ rseinfo = client.get_rse(rse=args.rse)
385
+ attributes = client.list_rse_attributes(rse=args.rse)
386
+ usage = client.get_rse_usage(rse=args.rse)
387
+ rse_limits = client.get_rse_limits(args.rse)
388
+
389
+ if cli_config == 'rich':
390
+ keyword_styles = {**CLITheme.BOOLEAN, **CLITheme.RSE_TYPE}
391
+ output = []
392
+ table_data = []
393
+ else:
394
+ print('Settings:')
395
+ print('=========')
396
+ for i, key in enumerate(sorted(rseinfo)):
397
+ if cli_config == 'rich':
398
+ if i == 0:
399
+ output.append('[b]Settings:[/]')
400
+ if key != 'protocols':
401
+ table_data.append([key, Text(str(rseinfo[key]), style=keyword_styles.get(str(rseinfo[key]), 'default'))])
402
+ else:
403
+ if key != 'protocols':
404
+ print(' ' + key + ': ' + str(rseinfo[key]))
405
+
406
+ if cli_config == 'rich':
407
+ table = generate_table(table_data, row_styles=['none'], col_alignments=['left', 'left'])
408
+ output.append(table)
409
+ table_data = []
410
+ else:
411
+ print('Attributes:')
412
+ print('===========')
413
+ for i, attribute in enumerate(sorted(attributes)):
414
+ if cli_config == 'rich':
415
+ if i == 0:
416
+ output.append('\n[b]Attributes:[/]')
417
+ table_data.append([attribute, Text(str(attributes[attribute]), style=keyword_styles.get(str(attributes[attribute]), 'default'))])
418
+ else:
419
+ print(' ' + attribute + ': ' + str(attributes[attribute]))
420
+
421
+ if cli_config == 'rich':
422
+ table = generate_table(table_data, row_styles=['none'], col_alignments=['left', 'left'])
423
+ output.append(table)
424
+ else:
425
+ print('Protocols:')
426
+ print('==========')
427
+ for i, protocol in enumerate(sorted(rseinfo['protocols'], key=lambda x: x['scheme'])):
428
+ if cli_config == 'rich':
429
+ if i == 0:
430
+ output.append('\n[b]Protocols:[/]')
431
+ output.append(Padding.indent(Text(protocol['scheme'], style=CLITheme.SUBHEADER_HIGHLIGHT), 2))
432
+ else:
433
+ print(' ' + protocol['scheme'])
434
+
435
+ table_data = []
436
+ for item in sorted(protocol):
437
+ if cli_config == 'rich':
438
+ if item == 'domains':
439
+ tree = Tree('')
440
+ for domain, values in protocol[item].items():
441
+ branch = tree.add(f'[{CLITheme.JSON_STR}]{domain}')
442
+ for k, v in values.items():
443
+ branch.add(f'[{CLITheme.JSON_STR}]{k}[/]: [{CLITheme.JSON_NUM}]{v}[/]')
444
+ table_data.append([item, tree])
445
+ else:
446
+ table_data.append([item, Text(str(protocol[item]), style=keyword_styles.get(protocol[item], 'default'))])
447
+ else:
448
+ if item == 'domains':
449
+ print(' ' + item + ': \'' + json.dumps(protocol[item]) + '\'')
450
+ else:
451
+ print(' ' + item + ': ' + str(protocol[item]))
452
+
453
+ if cli_config == 'rich':
454
+ table = generate_table(table_data, col_alignments=['left', 'left'], row_styles=['none'])
455
+ output.append(Padding.indent(table, 2))
456
+
457
+ if cli_config == 'rich':
458
+ header = ['SOURCE', 'USED', 'FILES', 'FREE', 'TOTAL', 'UPDATED AT']
459
+ key2id = {header[i].lower().replace(' ', '_'): i for i in range(len(header))}
460
+ table_data = []
461
+ else:
462
+ print('Usage:')
463
+ print('======')
464
+ for i, elem in enumerate(sorted(usage, key=lambda x: x['source'])):
465
+ if cli_config == 'rich':
466
+ if i == 0:
467
+ output.append('\n[b]Usage:[/]')
468
+
469
+ row = [''] * len(header)
470
+ row[0] = elem['source']
471
+ for item in sorted(elem):
472
+ if item in ['used', 'free', 'total']:
473
+ row[key2id[item]] = sizefmt(elem[item], True)
474
+ elif item != 'source' and item in key2id:
475
+ row[key2id[item]] = str(elem[item])
476
+ table_data.append(row)
477
+ else:
478
+ print(' ' + elem['source'])
479
+ for item in sorted(elem):
480
+ print(' ' + item + ': ' + str(elem[item]))
481
+
482
+ if cli_config == 'rich':
483
+ if len(table_data) > 0:
484
+ usage_table = generate_table(table_data, headers=header, col_alignments=['left', 'right', 'right', 'right', 'right', 'left'])
485
+ output.append(usage_table)
486
+ table_data = []
487
+ else:
488
+ print('RSE limits:')
489
+ print('===========')
490
+ for i, limit in enumerate(rse_limits):
491
+ if cli_config == 'rich':
492
+ if i == 0:
493
+ output.append('\n[b]RSE limits:[/]')
494
+ table_data.append([limit, str(rse_limits[limit]) + ' B'])
495
+ else:
496
+ print(' ' + limit + ': ' + str(rse_limits[limit]) + ' B')
497
+
498
+ if cli_config == 'rich':
499
+ if len(table_data) > 0:
500
+ table = generate_table(table_data, row_styles=['none'], col_alignments=['left', 'right'])
501
+ output.append(table)
502
+ spinner.stop()
503
+ print_output(*output, console=console, no_pager=args.no_pager)
504
+ return SUCCESS
505
+
506
+
507
+ @exception_handler
508
+ def set_attribute_rse(args, client, logger, console, spinner):
509
+ """
510
+ %(prog)s set-attribute [options] <field1=value1 field2=value2 ...>
511
+
512
+ Set RSE attributes.
513
+
514
+ """
515
+ client.add_rse_attribute(rse=args.rse, key=args.key, value=args.value)
516
+ print('Added new RSE attribute for %s: %s-%s ' % (args.rse, args.key, args.value))
517
+ return SUCCESS
518
+
519
+
520
+ @exception_handler
521
+ def get_attribute_rse(args, client, logger, console, spinner):
522
+ """
523
+ %(prog)s get-attribute [options] <field1=value1 field2=value2 ...>
524
+
525
+ Get RSE attributes.
526
+
527
+ """
528
+ attributes = client.list_rse_attributes(rse=args.rse)
529
+ if cli_config == 'rich':
530
+ table_data = [(k, Text(str(v), style=CLITheme.BOOLEAN.get(str(v), 'default'))) for k, v in sorted(attributes.items())]
531
+ table = generate_table(table_data, row_styles=['none'], col_alignments=['left', 'left'])
532
+ print_output(table, console=console, no_pager=args.no_pager)
533
+ else:
534
+ for k in attributes:
535
+ print(k + ': ' + str(attributes[k]))
536
+ return SUCCESS
537
+
538
+
539
+ @exception_handler
540
+ def delete_attribute_rse(args, client, logger, console, spinner):
541
+ """
542
+ %(prog)s delete-attribute [options] <field1=value1 field2=value2 ...>
543
+
544
+ Delete RSE attributes.
545
+
546
+ """
547
+ client.delete_rse_attribute(rse=args.rse, key=args.key)
548
+ print('Deleted RSE attribute for %s: %s-%s ' % (args.rse, args.key, args.value))
549
+ return SUCCESS
550
+
551
+
552
+ @exception_handler
553
+ def add_distance_rses(args, client, logger, console, spinner):
554
+ """
555
+ %(prog)s add-distance [options] SOURCE_RSE DEST_RSE
556
+
557
+ Set the distance between two RSEs.
558
+ """
559
+ params = {'distance': args.distance}
560
+ client.add_distance(args.source, args.destination, params)
561
+ print('Set distance from %s to %s to %d' % (args.source, args.destination, args.distance))
562
+ return SUCCESS
563
+
564
+
565
+ @exception_handler
566
+ def get_distance_rses(args, client, logger, console, spinner):
567
+ """
568
+ %(prog)s get-distance SOURCE_RSE DEST_RSE
569
+
570
+ Retrieve the existing distance information between two RSEs.
571
+ """
572
+ distance_info = client.get_distance(args.source, args.destination)
573
+ if cli_config == 'rich':
574
+ if distance_info:
575
+ table = generate_table([[args.source, args.destination, str(distance_info[0]['distance'])]], headers=['SOURCE', 'DESTINATION', 'DISTANCE'],
576
+ col_alignments=['left', 'left', 'right'])
577
+ print_output(table, console=console, no_pager=args.no_pager)
578
+ else:
579
+ print(f"No distance set from {args.source} to {args.destination}")
580
+ else:
581
+ if distance_info:
582
+ print('Distance information from %s to %s: distance=%d' % (args.source, args.destination, distance_info[0]['distance']))
583
+ else:
584
+ print("No distance set from %s to %s" % (args.source, args.destination))
585
+ return SUCCESS
586
+
587
+
588
+ @exception_handler
589
+ def update_distance_rses(args, client, logger, console, spinner):
590
+ """
591
+ %(prog)s update-distance [options] SOURCE_RSE DEST_RSE
592
+
593
+ Update the existing distance entry between two RSEs.
594
+ """
595
+ params = {}
596
+ if args.distance is not None:
597
+ params['distance'] = args.distance
598
+ elif args.ranking is not None:
599
+ params['distance'] = args.ranking
600
+ client.update_distance(args.source, args.destination, params)
601
+ print('Update distance information from %s to %s:' % (args.source, args.destination))
602
+ if params.get('distance') is not None:
603
+ print("- Distance set to %d" % params['distance'])
604
+ return SUCCESS
605
+
606
+
607
+ @exception_handler
608
+ def delete_distance_rses(args, client, logger, console, spinner):
609
+ """
610
+ %(prog)s delete-distance [options] SOURCE_RSE DEST_RSE
611
+
612
+ Update the existing distance entry between two RSEs.
613
+ """
614
+ client.delete_distance(args.source, args.destination)
615
+ print('Deleted distance information from %s to %s.' % (args.source, args.destination))
616
+ return SUCCESS
617
+
618
+
619
+ @exception_handler
620
+ def add_protocol_rse(args, client, logger, console, spinner):
621
+ """
622
+ %(prog)s add-protocol-rse [options] <rse>
623
+
624
+ Add a new protocol handler for an RSE
625
+ """
626
+ proto = {'hostname': args.hostname,
627
+ 'scheme': args.scheme,
628
+ 'port': args.port,
629
+ 'impl': args.impl,
630
+ 'prefix': args.prefix}
631
+ if args.domain_json:
632
+ proto['domains'] = args.domain_json
633
+ proto.setdefault('extended_attributes', {})
634
+ if args.ext_attr_json:
635
+ proto['extended_attributes'] = args.ext_attr_json
636
+ if proto['scheme'] == 'srm' and not args.web_service_path:
637
+ print('Error: space-token and web-service-path must be provided for SRM endpoints.')
638
+ return FAILURE
639
+ if args.space_token:
640
+ proto['extended_attributes']['space_token'] = args.space_token
641
+ if args.web_service_path:
642
+ proto['extended_attributes']['web_service_path'] = args.web_service_path
643
+ # Rucio 1.14.1 chokes on an empty extended_attributes key.
644
+ if not proto['extended_attributes']:
645
+ del proto['extended_attributes']
646
+ client.add_protocol(args.rse, proto)
647
+ return SUCCESS
648
+
649
+
650
+ @exception_handler
651
+ def del_protocol_rse(args, client, logger, console, spinner):
652
+ """
653
+ %(prog)s delete-protocol-rse [options] <rse>
654
+
655
+ Remove a protocol handler for a RSE
656
+ """
657
+ kwargs = {}
658
+ if args.port:
659
+ kwargs['port'] = args.port
660
+ if args.hostname:
661
+ kwargs['hostname'] = args.hostname
662
+ client.delete_protocols(args.rse, args.scheme, **kwargs)
663
+
664
+
665
+ @exception_handler
666
+ def add_qos_policy(args, client, logger, console, spinner):
667
+ """
668
+ %(prog)s add-qos-policy <rse> <qos_policy>
669
+
670
+ Add a QoS policy to an RSE.
671
+ """
672
+ client.add_qos_policy(args.rse, args.qos_policy)
673
+ print('Added QoS policy to RSE %s: %s' % (args.rse, args.qos_policy))
674
+ return SUCCESS
675
+
676
+
677
+ @exception_handler
678
+ def delete_qos_policy(args, client, logger, console, spinner):
679
+ """
680
+ %(prog)s delete-qos-policy <rse> <qos_policy>
681
+
682
+ Delete a QoS policy from an RSE.
683
+ """
684
+ client.delete_qos_policy(args.rse, args.qos_policy)
685
+ print('Deleted QoS policy from RSE %s: %s' % (args.rse, args.qos_policy))
686
+ return SUCCESS
687
+
688
+
689
+ @exception_handler
690
+ def list_qos_policies(args, client, logger, console, spinner):
691
+ """
692
+ %(prog)s list-qos-policies <rse>
693
+
694
+ List all QoS policies of an RSE.
695
+ """
696
+ if cli_config == 'rich':
697
+ spinner.update(status='Fetching QoS policies')
698
+ spinner.start()
699
+
700
+ qos_policies = client.list_qos_policies(args.rse)
701
+ if cli_config == 'rich':
702
+ qos_policies = [[qos_policy] for qos_policy in sorted(qos_policies)]
703
+ table = generate_table(qos_policies, headers=['QOS POLICY'], col_alignments=['left'])
704
+ spinner.stop()
705
+ print_output(table, console=console, no_pager=args.no_pager)
706
+ else:
707
+ for qos_policy in sorted(qos_policies):
708
+ print(qos_policy)
709
+ return SUCCESS
710
+
711
+
712
+ @exception_handler
713
+ def set_limit_rse(args, client, logger, console, spinner):
714
+ """
715
+ %(prog)s set-limit <rse> <name> <value>
716
+
717
+ Set the RSE limit given the rse name and the name and value of the limit
718
+ """
719
+ try:
720
+ args.value = int(args.value)
721
+ if client.set_rse_limits(args.rse, args.name, args.value):
722
+ logger.info('Set RSE limit successfully for %s: %s = %s' % (args.rse, args.name, args.value))
723
+ except ValueError:
724
+ logger.error('The RSE limit value must be an integer')
725
+
726
+ return SUCCESS
727
+
728
+
729
+ @exception_handler
730
+ def delete_limit_rse(args, client, logger, console, spinner):
731
+ """
732
+ %(prog)s delete-limit <rse> <name>
733
+
734
+ Delete the RSE limit given the rse name and the name of the limit
735
+ """
736
+ limits = client.get_rse_limits(args.rse)
737
+ if args.name not in limits.keys():
738
+ logger.error('Limit %s not defined in RSE %s' % (args.name, args.rse))
739
+ else:
740
+ if client.delete_rse_limits(args.rse, args.name):
741
+ logger.info('Deleted RSE limit successfully for %s: %s' % (args.rse, args.name))
742
+
743
+ return SUCCESS
744
+
745
+
746
+ @exception_handler
747
+ def add_scope(args, client, logger, console, spinner):
748
+ """
749
+ %(prog)s add [options] <field1=value1 field2=value2 ...>
750
+
751
+ Add scope.
752
+
753
+ """
754
+ client.add_scope(account=args.account, scope=args.scope)
755
+ print('Added new scope to account: %s-%s' % (args.scope, args.account))
756
+ return SUCCESS
757
+
758
+
759
+ @exception_handler
760
+ def list_scopes(args, client, logger, console, spinner):
761
+ """
762
+ %(prog)s list [options] <field1=value1 field2=value2 ...>
763
+
764
+ List scopes.
765
+
766
+ """
767
+ if cli_config == 'rich':
768
+ spinner.update(status='Fetching scopes')
769
+ spinner.start()
770
+
771
+ if args.account:
772
+ scopes = client.list_scopes_for_account(args.account)
773
+ else:
774
+ scopes = client.list_scopes()
775
+ if cli_config == 'rich':
776
+ scopes = [[scope] for scope in sorted(scopes) if 'mock' not in scope]
777
+ table = generate_table(scopes, headers=['SCOPE'], col_alignments=['left'])
778
+ spinner.stop()
779
+ print_output(table, console=console, no_pager=args.no_pager)
780
+ else:
781
+ for scope in scopes:
782
+ if 'mock' not in scope:
783
+ print(scope)
784
+ return SUCCESS
785
+
786
+
787
+ @exception_handler
788
+ def get_config(args, client, logger, console, spinner):
789
+ """
790
+ %(prog)s get [options] <field1=value1 field2=value2 ...>
791
+
792
+ Get the configuration. Either everything, or matching the given section/option.
793
+ """
794
+ res = client.get_config(section=args.section, option=args.option)
795
+ if not isinstance(res, dict):
796
+ print('[%s]\n%s=%s' % (args.section, args.option, str(res)))
797
+ else:
798
+ print_header = True
799
+ for i in list(res.keys()):
800
+ if print_header:
801
+ if args.section is not None:
802
+ print('[%s]' % args.section)
803
+ else:
804
+ print('[%s]' % i)
805
+ if not isinstance(res[i], dict):
806
+ print('%s=%s' % (i, str(res[i])))
807
+ print_header = False
808
+ else:
809
+ for j in list(res[i].keys()):
810
+ print('%s=%s' % (j, str(res[i][j])))
811
+ return SUCCESS
812
+
813
+
814
+ @exception_handler
815
+ def set_config_option(args, client, logger, console, spinner):
816
+ """
817
+ %(prog)s set [options] <field1=value1 field2=value2 ...>
818
+
819
+ Set the configuration value for a matching section/option. Missing section/option will be created.
820
+ """
821
+ client.set_config_option(section=args.section, option=args.option, value=args.value)
822
+ print('Set configuration: %s.%s=%s' % (args.section, args.option, args.value))
823
+ return SUCCESS
824
+
825
+
826
+ @exception_handler
827
+ def delete_config_option(args, client, logger, console, spinner):
828
+ """
829
+ %(prog)s delete [options] <field1=value1 field2=value2 ...>
830
+
831
+ Delete a configuration option from a section
832
+ """
833
+ if client.delete_config_option(section=args.section, option=args.option):
834
+ print('Deleted section \'%s\' option \'%s\'' % (args.section, args.option))
835
+ else:
836
+ print('Section \'%s\' option \'%s\' not found' % (args.section, args.option))
837
+ return SUCCESS
838
+
839
+
840
+ @exception_handler
841
+ def add_subscription(args, client, logger, console, spinner):
842
+ """
843
+ %(prog)s add [options] name Filter replication_rules
844
+
845
+ Add subscription.
846
+
847
+ """
848
+ if args.subs_account:
849
+ account = args.subs_account
850
+ elif args.issuer:
851
+ account = args.issuer
852
+ else:
853
+ account = client.account
854
+ subscription_id = client.add_subscription(name=args.name, account=account, filter_=json.loads(args.filter), replication_rules=json.loads(args.replication_rules),
855
+ comments=args.comments, lifetime=args.lifetime, retroactive=False, dry_run=False, priority=args.priority)
856
+ print('Subscription added %s' % (subscription_id))
857
+ return SUCCESS
858
+
859
+
860
+ @exception_handler
861
+ def list_subscriptions(args, client, logger, console, spinner):
862
+ """
863
+ %(prog)s list [options] [name]
864
+
865
+ List subscriptions.
866
+
867
+ """
868
+ if args.subs_account:
869
+ account = args.subs_account
870
+ elif args.issuer:
871
+ account = args.issuer
872
+ else:
873
+ account = client.account
874
+
875
+ if cli_config == 'rich':
876
+ spinner.update(status='Fetching subscriptions')
877
+ spinner.start()
878
+ keyword_styles = {**CLITheme.SUBSCRIPTION_STATE, **CLITheme.BOOLEAN}
879
+
880
+ subs = client.list_subscriptions(name=args.name, account=account)
881
+ for sub in subs:
882
+ table_data = []
883
+ if args.long:
884
+ if cli_config == 'rich':
885
+ for k, v in sorted(sub.items()):
886
+ if k == 'filter':
887
+ filter_tree = Tree('')
888
+ for filter, values in json.loads(sub['filter']).items():
889
+ values_str = ', '.join(values)
890
+ filter_tree.add(f'[{CLITheme.JSON_STR}]{filter}[/]: {values_str}')
891
+ table_data.append(['filter', filter_tree])
892
+ elif k == 'replication_rules':
893
+ rule_tree = Tree('')
894
+ for i, rule in enumerate(json.loads(sub['replication_rules'])):
895
+ branch = rule_tree.add(Text('rule:', style='default'))
896
+ for k, v in rule.items():
897
+ branch.add(f'[{CLITheme.JSON_STR}]{k}[/]: {v}')
898
+ table_data.append(['replication_rules', rule_tree])
899
+ else:
900
+ table_data.append([str(k), Text(str(v), style=keyword_styles.get(str(v), 'default'))])
901
+ else:
902
+ print('\n'.join('%s: %s' % (str(k), str(v)) for (k, v) in list(sub.items())))
903
+ print()
904
+ else:
905
+ if cli_config == 'rich':
906
+ table_data.append(['account', sub['account']])
907
+ table_data.append(['comments', sub.get('comments', '')])
908
+ filter_tree = Tree('')
909
+ for filter, values in json.loads(sub['filter']).items():
910
+ values_str = ', '.join(values)
911
+ filter_tree.add(f'[green]{filter}[/]: {values_str}')
912
+ table_data.append(['filter', filter_tree])
913
+ table_data.append(['name', sub['name']])
914
+ table_data.append(['policyid', str(sub['policyid'])])
915
+ rule_tree = Tree('')
916
+ for i, rule in enumerate(json.loads(sub['replication_rules'])):
917
+ branch = rule_tree.add(Text('rule:', style='default'))
918
+ for k, v in rule.items():
919
+ branch.add(f'[{CLITheme.JSON_STR}]{k}[/]: {v}')
920
+ table_data.append(['replication_rules', rule_tree])
921
+ table_data.append(['state', Text(str(sub['state']), keyword_styles.get(str(sub['state']), 'default'))])
922
+ else:
923
+ print("%s: %s %s\n priority: %s\n filter: %s\n rules: %s\n comments: %s" % (sub['account'], sub['name'], sub['state'], sub['policyid'], sub['filter'], sub['replication_rules'], sub.get('comments', '')))
924
+
925
+ if cli_config == 'rich':
926
+ table = generate_table(table_data, row_styles=['none'], col_alignments=['left', 'left'])
927
+ spinner.stop()
928
+ print_output(table, console=console, no_pager=args.no_pager)
929
+ return SUCCESS
930
+
931
+
932
+ @exception_handler
933
+ def update_subscription(args, client, logger, console, spinner):
934
+ """
935
+ %(prog)s update [options] name filter replication_rules
936
+
937
+ Update a subscription.
938
+
939
+ """
940
+ if args.subs_account:
941
+ account = args.subs_account
942
+ elif args.issuer:
943
+ account = args.issuer
944
+ else:
945
+ account = client.account
946
+ client.update_subscription(name=args.name, account=account, filter_=json.loads(args.filter), replication_rules=json.loads(args.replication_rules),
947
+ comments=args.comments, lifetime=args.lifetime, retroactive=False, dry_run=False, priority=args.priority)
948
+ return SUCCESS
949
+
950
+
951
+ @exception_handler
952
+ def reevaluate_did_for_subscription(args, client, logger, console, spinner):
953
+ """
954
+ %(prog)s reevaulate [options] dids
955
+
956
+ Reevaluate a list of DIDs against all active subscriptions.
957
+
958
+ """
959
+ for did in args.dids.split(','):
960
+ scope, name = get_scope(did, client)
961
+ client.set_metadata(scope, name, 'is_new', True)
962
+ return SUCCESS
963
+
964
+
965
+ @exception_handler
966
+ def list_account_attributes(args, client, logger, console, spinner):
967
+ """
968
+ %(prog)s show [options] <field1=value1 field2=value2 ...>
969
+
970
+ List the attributes for an account.
971
+
972
+ """
973
+ account = args.account or client.account
974
+ attributes = next(client.list_account_attributes(account))
975
+ table_data = []
976
+ for attr in attributes:
977
+ table_data.append([attr['key'], attr['value']])
978
+
979
+ if cli_config == 'rich':
980
+ table = generate_table(table_data, headers=['Key', 'Value'], col_alignments=['left', 'left'])
981
+ print_output(table, console=console, no_pager=args.no_pager)
982
+ else:
983
+ print(tabulate(table_data, tablefmt=tablefmt, headers=['Key', 'Value']))
984
+ return SUCCESS
985
+
986
+
987
+ @exception_handler
988
+ def add_account_attribute(args, client, logger, console, spinner):
989
+ """
990
+ %(prog)s show [options] <field1=value1 field2=value2 ...>
991
+
992
+ Add attribute for an account.
993
+
994
+ """
995
+ client.add_account_attribute(account=args.account, key=args.key, value=args.value)
996
+ return SUCCESS
997
+
998
+
999
+ @exception_handler
1000
+ def delete_account_attribute(args, client, logger, console, spinner):
1001
+ """
1002
+ %(prog)s show [options] <field1=value1 field2=value2 ...>
1003
+
1004
+ Delete attribute for an account.
1005
+
1006
+ """
1007
+ client.delete_account_attribute(account=args.account, key=args.key)
1008
+ return SUCCESS
1009
+
1010
+
1011
+ @exception_handler
1012
+ def quarantine_replicas(args, client, logger, console, spinner):
1013
+ """
1014
+ %(prog)s quarantine --rse <rse> (--paths <file with replica paths>|<path> ...)
1015
+ Quarantine replicas
1016
+ """
1017
+ chunk = []
1018
+
1019
+ # send requests in chunks
1020
+ chunk_size = 1000
1021
+
1022
+ rse = args.rse
1023
+ if args.paths_list:
1024
+ replicas_list = args.paths_list
1025
+ else:
1026
+ replicas_list = open(args.paths_file, "r") # will iterate over file lines
1027
+
1028
+ for line in replicas_list:
1029
+ path = line.strip()
1030
+ if path: # skip blank lines
1031
+ chunk.append(dict(path=path))
1032
+ if len(chunk) >= chunk_size:
1033
+ client.quarantine_replicas(chunk, rse=rse)
1034
+ chunk = []
1035
+ if chunk:
1036
+ client.quarantine_replicas(chunk, rse=rse)
1037
+ return SUCCESS
1038
+
1039
+
1040
+ def __declare_bad_file_replicas_by_lfns(args: object, client) -> object:
1041
+ """
1042
+ Declare a list of bad replicas using RSE name, scope and list of LFNs.
1043
+ """
1044
+ if not args.scope or not args.rse:
1045
+ print("--lfns requires using --rse and --scope")
1046
+ return FAILURE
1047
+ reason = args.reason
1048
+ scope = args.scope
1049
+ rse = args.rse
1050
+ replicas = []
1051
+
1052
+ # send requests in chunks
1053
+ chunk_size = 10000
1054
+
1055
+ def do_declare(client, lst, reason):
1056
+ non_declared = client.declare_bad_file_replicas(lst, reason)
1057
+ for rse, undeclared in non_declared.items():
1058
+ for r in undeclared:
1059
+ print(f'{rse} : replica cannot be declared: {r}')
1060
+
1061
+ for line in open(args.lfns, "r"):
1062
+ lfn = line.strip()
1063
+ if lfn:
1064
+ replicas.append({"scope": scope, "rse": rse, "name": lfn})
1065
+ if len(replicas) >= chunk_size:
1066
+ do_declare(client, replicas, reason)
1067
+ replicas = []
1068
+ if replicas:
1069
+ do_declare(client, replicas, reason)
1070
+ return SUCCESS
1071
+
1072
+
1073
+ @exception_handler
1074
+ def declare_bad_file_replicas(args, client, logger, console, spinner):
1075
+ """
1076
+
1077
+ Declare replicas as bad.
1078
+
1079
+ """
1080
+
1081
+ if args.lfns:
1082
+ return __declare_bad_file_replicas_by_lfns(args, client)
1083
+
1084
+ if args.inputfile:
1085
+ with open(args.inputfile) as infile:
1086
+ bad_files = list(filter(None, [line.strip() for line in infile]))
1087
+ else:
1088
+ bad_files = args.listbadfiles
1089
+
1090
+ # Interpret filenames not in scheme://* format as LFNs and convert them to PFNs
1091
+ bad_files_pfns = []
1092
+ for bad_file in bad_files:
1093
+ if bad_file.find('://') == -1:
1094
+ scope, name = get_scope(bad_file, client)
1095
+ did_info = client.get_did(scope, name)
1096
+ if did_info['type'].upper() != 'FILE' and not args.allow_collection:
1097
+ print('DID %s:%s is a collection and --allow-collection was not specified.' % (scope, name))
1098
+ return FAILURE
1099
+ replicas = [replica for rep in client.list_replicas([{'scope': scope, 'name': name}])
1100
+ for replica in list(rep['pfns'].keys())]
1101
+ bad_files_pfns.extend(replicas)
1102
+ else:
1103
+ bad_files_pfns.append(bad_file)
1104
+ if args.verbose:
1105
+ print("PFNs that will be declared bad:")
1106
+ for pfn in bad_files_pfns:
1107
+ print(pfn)
1108
+
1109
+ if len(bad_files_pfns) < 100:
1110
+ # Using the old API to declare
1111
+ non_declared = client.declare_bad_file_replicas(bad_files_pfns, args.reason)
1112
+ for rse in non_declared:
1113
+ for pfn in non_declared[rse]:
1114
+ print('%s : PFN %s cannot be declared.' % (rse, pfn))
1115
+ else:
1116
+ print('Getting the information about RSE protocols. It can take several seconds')
1117
+ dict_rse = client.export_data(distance=False)
1118
+ prot_dict = {}
1119
+ for rse, dict_attr in dict_rse['rses'].items():
1120
+ protocols = dict_attr['protocols']
1121
+ for prot in protocols:
1122
+ prot_dict[str('%s://%s%s' % (prot['scheme'], prot['hostname'], prot['prefix']))] = rse
1123
+ prot_dict[str('%s://%s:%s%s' % (prot['scheme'], prot['hostname'], prot['port'], prot['prefix']))] = rse
1124
+ print('Protocol information retrieved')
1125
+
1126
+ chunk_size = 10000
1127
+ print('Starting the declaration by chunks of %s' % chunk_size)
1128
+ tot_files = len(bad_files)
1129
+ tot_file_declared = 0
1130
+ cnt = 0
1131
+ nchunk = math.ceil(tot_files / chunk_size)
1132
+ for chunk in chunks(bad_files_pfns, chunk_size):
1133
+ list_bad_pfns = []
1134
+ cnt += 1
1135
+ previous_pattern = None
1136
+ for pfn in clean_pfns(chunk):
1137
+ unknown = True
1138
+ if previous_pattern:
1139
+ if previous_pattern in pfn:
1140
+ list_bad_pfns.append(pfn)
1141
+ unknown = False
1142
+ continue
1143
+ for pattern in prot_dict:
1144
+ if pattern in pfn:
1145
+ previous_pattern = prot_dict[pattern]
1146
+ list_bad_pfns.append(pfn)
1147
+ unknown = False
1148
+ break
1149
+ if unknown:
1150
+ print('Cannot find any RSE associated to %s' % pfn)
1151
+ client.add_bad_pfns(pfns=list_bad_pfns, reason=args.reason, state='BAD', expires_at=None)
1152
+ ndeclared = len(list_bad_pfns)
1153
+ tot_file_declared += ndeclared
1154
+ print('Chunk %s/%s : %s replicas successfully declared' % (int(cnt), int(nchunk), ndeclared))
1155
+ print('--------------------------------')
1156
+ print('Summary')
1157
+ print('%s/%s replicas successfully declared' % (tot_file_declared, tot_files))
1158
+
1159
+ return SUCCESS
1160
+
1161
+
1162
+ @exception_handler
1163
+ def declare_temporary_unavailable_replicas(args, client, logger, console, spinner):
1164
+ """
1165
+ %(prog)s show [options] <field1=value1 field2=value2 ...>
1166
+
1167
+ Declare a list of temporary unavailable replicas.
1168
+
1169
+ """
1170
+ bad_files = []
1171
+ if args.inputfile:
1172
+ with open(args.inputfile) as infile:
1173
+ for line in infile:
1174
+ bad_file = line.rstrip('\n')
1175
+ if '://' not in bad_file:
1176
+ print('%s is not a valid PFN. Aborting', bad_file)
1177
+ return FAILURE
1178
+ if bad_file != '':
1179
+ bad_files.append(bad_file)
1180
+ else:
1181
+ bad_files = args.listbadfiles
1182
+
1183
+ if args.duration is None:
1184
+ raise ValueError("Duration should have been set, something went wrong!")
1185
+
1186
+ expiration_date = (datetime.datetime.utcnow() + datetime.timedelta(seconds=args.duration)).isoformat()
1187
+
1188
+ chunk_size = 10000
1189
+ tot_files = len(bad_files)
1190
+ cnt = 0
1191
+ nchunk = math.ceil(tot_files / chunk_size)
1192
+ for chunk in chunks(bad_files, chunk_size):
1193
+ cnt += 1
1194
+ client.add_bad_pfns(pfns=chunk, reason=args.reason, state='TEMPORARY_UNAVAILABLE', expires_at=expiration_date)
1195
+ ndeclared = len(chunk)
1196
+ print('Chunk %s/%s : %s replicas successfully declared' % (int(cnt), int(nchunk), ndeclared))
1197
+ print('--------------------------------')
1198
+ print('Summary')
1199
+ print('%s replicas successfully declared' % tot_files)
1200
+
1201
+ return SUCCESS
1202
+
1203
+
1204
+ @exception_handler
1205
+ def list_pfns(args, client, logger, console, spinner):
1206
+ """
1207
+ %(prog)s list [options] <field1=value1 field2=value2 ...>
1208
+
1209
+ List the possible PFN for a file at a site.
1210
+
1211
+ """
1212
+ dids = args.dids.split(',')
1213
+ rse = args.rse
1214
+ protocol = args.protocol
1215
+ for input_did in dids:
1216
+ scope, name = get_scope(input_did, client)
1217
+ replicas = [rep for rep in client.list_replicas([{'scope': scope, 'name': name}, ], schemes=[protocol, ])]
1218
+ if rse in replicas[0]['rses'] and replicas[0]['rses'][rse]:
1219
+ print(replicas[0]['rses'][rse][0])
1220
+ else:
1221
+ logger.warning('The file has no replica on the specified RSE')
1222
+ rse_info = rsemgr.get_rse_info(rse, vo=client.vo)
1223
+ proto = rsemgr.create_protocol(rse_info, 'read', scheme=protocol)
1224
+ try:
1225
+ pfn = proto.lfns2pfns(lfns={'scope': scope, 'name': name})
1226
+ result = list(pfn.values())[0]
1227
+ except ReplicaNotFound as error:
1228
+ result = error
1229
+ if isinstance(result, (RSEOperationNotSupported, ReplicaNotFound)):
1230
+ if not rse_info['deterministic']:
1231
+ logger.warning('This is a non-deterministic site, so the real PFN might be different from the on suggested')
1232
+ rse_attr = client.list_rse_attributes(rse)
1233
+ naming_convention = rse_attr.get(RseAttr.NAMING_CONVENTION, None)
1234
+ parents = [did for did in client.list_parent_dids(scope, name)]
1235
+ if len(parents) > 1:
1236
+ logger.warning('The file has multiple parents')
1237
+ for did in parents:
1238
+ if did['type'] == 'DATASET':
1239
+ path = construct_non_deterministic_pfn(did['name'], scope, name, naming_convention=naming_convention)
1240
+ pfn = ''.join([proto.attributes['scheme'],
1241
+ '://',
1242
+ proto.attributes['hostname'],
1243
+ ':',
1244
+ str(proto.attributes['port']),
1245
+ proto.attributes['prefix'],
1246
+ path if not path.startswith('/') else path[1:]])
1247
+ print(pfn)
1248
+ else:
1249
+ logger.error('Unexpected error')
1250
+ return FAILURE
1251
+ else:
1252
+ print(result)
1253
+ return SUCCESS
1254
+
1255
+
1256
+ @exception_handler
1257
+ def import_data(args, client, logger, console, spinner):
1258
+ """
1259
+ %(prog)s list [options] <field1=value1 field2=value2 ...>
1260
+
1261
+ Import data from JSON file to Rucio.
1262
+
1263
+ """
1264
+ import_file_path = args.file_path
1265
+ data = None
1266
+ if cli_config == 'rich':
1267
+ spinner.update(status='Reading file')
1268
+ spinner.start()
1269
+
1270
+ try:
1271
+ with open(import_file_path) as import_file:
1272
+ data_string = import_file.read()
1273
+ data = parse_response(data_string)
1274
+ except ValueError as error:
1275
+ if cli_config == 'rich':
1276
+ spinner.stop()
1277
+ print_output(f'{CLITheme.FAILURE_ICON} There was problem with decoding your file.', console=console, no_pager=True)
1278
+ logger.error(error)
1279
+ else:
1280
+ print('There was problem with decoding your file.')
1281
+ print(error)
1282
+ return FAILURE
1283
+ except OSError as error:
1284
+ if cli_config == 'rich':
1285
+ spinner.stop()
1286
+ print_output(f'{CLITheme.FAILURE_ICON} There was a problem with reading your file.', console=console, no_pager=True)
1287
+ logger.error(error)
1288
+ else:
1289
+ print('There was a problem with reading your file.')
1290
+ print(error)
1291
+ return FAILURE
1292
+
1293
+ if data:
1294
+ client.import_data(data)
1295
+ if cli_config == 'rich':
1296
+ spinner.stop()
1297
+ print_output(f'{CLITheme.SUCCESS_ICON} Data successfully imported.', console=console, no_pager=True)
1298
+ else:
1299
+ print('Data successfully imported.')
1300
+ return SUCCESS
1301
+ else:
1302
+ if cli_config == 'rich':
1303
+ spinner.stop()
1304
+ print_output('Nothing to import.', console=console, no_pager=True)
1305
+ else:
1306
+ print('Nothing to import.')
1307
+ return FAILURE
1308
+
1309
+
1310
+ @exception_handler
1311
+ def export_data(args, client, logger, console, spinner):
1312
+ """
1313
+ %(prog)s list [options] <field1=value1 field2=value2 ...>
1314
+
1315
+ Export data from Rucio to JSON file.
1316
+
1317
+ """
1318
+ destination_file_path = args.file_path
1319
+ if cli_config == 'rich':
1320
+ spinner.update(status='Querying data')
1321
+ spinner.start()
1322
+ else:
1323
+ print('Start querying data.')
1324
+
1325
+ data = client.export_data()
1326
+ try:
1327
+ with open(destination_file_path, 'w+') as destination_file:
1328
+ destination_file.write(render_json(**data))
1329
+ if cli_config != 'rich':
1330
+ print('File successfully written.')
1331
+ if cli_config == 'rich':
1332
+ spinner.stop()
1333
+ print_output(f'{CLITheme.SUCCESS_ICON} Data successfully exported to {args.file_path}', console=console, no_pager=True)
1334
+ else:
1335
+ print('Data successfully exported to %s' % args.file_path)
1336
+ return SUCCESS
1337
+ except OSError as error:
1338
+ if cli_config == 'rich':
1339
+ spinner.stop()
1340
+ print_output(f'{CLITheme.FAILURE_ICON} There was a problem with reading your file.', console=console, no_pager=True)
1341
+ logger.error(error)
1342
+ else:
1343
+ print('There was a problem with reading your file.')
1344
+ print(error)
1345
+ return FAILURE
1346
+
1347
+
1348
+ @exception_handler
1349
+ def set_tombstone(args, client, logger, console, spinner):
1350
+ """
1351
+ %(prog)s list [options] <field1=value1 field2=value2 ...>
1352
+
1353
+ Set a tombstone on a list of replicas.
1354
+ """
1355
+ dids = args.dids
1356
+ rse = args.rse
1357
+ dids = [dids] if ',' not in dids else dids.split(',')
1358
+ replicas = []
1359
+ for did in dids:
1360
+ scope, name = get_scope(did, client)
1361
+ replicas.append({'scope': scope, 'name': name, 'rse': rse})
1362
+ client.set_tombstone(replicas)
1363
+ logger.info('Set tombstone successfully on: %s' % args.dids)
1364
+ return SUCCESS
1365
+
1366
+
1367
+ def get_parser():
1368
+ """
1369
+ Returns the argparse parser.
1370
+ """
1371
+ oparser = argparse.ArgumentParser(prog=os.path.basename(sys.argv[0]), add_help=True)
1372
+
1373
+ # required argument was added in Python 3.7 and restores the Python 2 behavior
1374
+ required_arg = {'required': True}
1375
+
1376
+ subparsers = oparser.add_subparsers(dest='subcommand', **required_arg)
1377
+
1378
+ # Main arguments
1379
+ oparser.add_argument('--version', action='version', version='%(prog)s ' + version.version_string())
1380
+ oparser.add_argument('--verbose', '-v', default=False, action='store_true', help="Print more verbose output")
1381
+ oparser.add_argument('-H', '--host', dest="host", metavar="ADDRESS", help="The Rucio API host")
1382
+ oparser.add_argument('--auth-host', dest="auth_host", metavar="ADDRESS", help="The Rucio Authentication host")
1383
+ oparser.add_argument('-a', '--account', dest="issuer", metavar="ACCOUNT", help="Rucio account to use")
1384
+ oparser.add_argument('-S', '--auth-strategy', dest="auth_strategy", default=None, help="Authentication strategy (userpass, x509, ssh ...)")
1385
+ oparser.add_argument('-T', '--timeout', dest="timeout", type=float, default=None, help="Set all timeout values to SECONDS")
1386
+ oparser.add_argument('--user-agent', '-U', dest="user_agent", default='rucio-clients', action='store', help="Rucio User Agent")
1387
+ oparser.add_argument('--vo', dest="vo", metavar="VO", default=None, help="VO to authenticate at. Only used in multi-VO mode.")
1388
+ oparser.add_argument("--no-pager", dest="no_pager", default=False, action='store_true', help=argparse.SUPPRESS)
1389
+
1390
+ # Options for the userpass auth_strategy
1391
+ oparser.add_argument('-u', '--user', dest='username', default=None, help='username')
1392
+ oparser.add_argument('-pwd', '--password', dest='password', default=None, help='password')
1393
+ # Options for defining the OIDC scope# Options for defining remaining OIDC parameters
1394
+ oparser.add_argument('--oidc-user', dest='oidc_username', default=None, help='OIDC username')
1395
+ oparser.add_argument('--oidc-password', dest='oidc_password', default=None, help='OIDC password')
1396
+ oparser.add_argument('--oidc-scope', dest='oidc_scope', default='openid profile', help='Defines which (OIDC) information user will share with Rucio. '
1397
+ + 'Rucio requires at least -sc="openid profile". To request refresh token for Rucio, scope must include "openid offline_access" and ' # NOQA: W503
1398
+ + 'there must be no active access token saved on the side of the currently used Rucio Client.') # NOQA: W503
1399
+ oparser.add_argument('--oidc-audience', dest='oidc_audience', default=None, help='Defines which audience are tokens requested for.')
1400
+ oparser.add_argument('--oidc-auto', dest='oidc_auto', default=False, action='store_true', help='If not specified, username and password credentials are not required and users will be given a URL '
1401
+ + 'to use in their browser. If specified, the users explicitly trust Rucio with their IdP credentials.') # NOQA: W503
1402
+ oparser.add_argument('--oidc-polling', dest='oidc_polling', default=False, action='store_true', help='If not specified, user will be asked to enter a code returned by the browser to the command line. '
1403
+ + 'If --polling is set, Rucio Client should get the token without any further interaction of the user. This option is active only if --auto is *not* specified.') # NOQA: W503
1404
+ oparser.add_argument('--oidc-refresh-lifetime', dest='oidc_refresh_lifetime', default=None, help='Max lifetime in hours for this an access token will be refreshed by asynchronous Rucio daemon. '
1405
+ + 'If not specified, refresh will be stopped after 4 days. This option is effective only if --oidc-scope includes offline_access scope for a refresh token to be granted to Rucio.') # NOQA: W503
1406
+ oparser.add_argument('--oidc-issuer', dest='oidc_issuer', default=None,
1407
+ help='Defines which Identity Provider is going to be used. The issuer string must correspond '
1408
+ + 'to the keys configured in the /etc/idpsecrets.json auth server configuration file.') # NOQA: W503
1409
+
1410
+ # Options for the x509 auth_strategy
1411
+ oparser.add_argument('--certificate', dest='certificate', default=None, help='Client certificate file')
1412
+ oparser.add_argument('--client-key', dest='client_key', default=None, help='Client key for x509 Authentication.')
1413
+ oparser.add_argument('--ca-certificate', dest='ca_certificate', default=None, help='CA certificate to verify peer against (SSL)')
1414
+
1415
+ # The import export subparser
1416
+ data_parser = subparsers.add_parser('data', help='Import and export data')
1417
+ data_subparsers = data_parser.add_subparsers(dest='data_subcommand', **required_arg)
1418
+
1419
+ # The import command
1420
+ import_parser = data_subparsers.add_parser('import',
1421
+ help='Import data to Rucio from JSON file.',
1422
+ formatter_class=argparse.RawDescriptionHelpFormatter,
1423
+ epilog='Usage example\n'
1424
+ '"""""""""""""\n'
1425
+ 'Import data from the file file.json::\n'
1426
+ '\n'
1427
+ ' $ rucio-admin data import file.json\n'
1428
+ '\n')
1429
+ import_parser.add_argument('file_path', action='store', help='File path.')
1430
+ import_parser.set_defaults(which='import')
1431
+
1432
+ # The export command
1433
+ export_parser = data_subparsers.add_parser('export',
1434
+ help='Export data from Rucio to JSON file.',
1435
+ formatter_class=argparse.RawDescriptionHelpFormatter,
1436
+ epilog='Usage example\n'
1437
+ '"""""""""""""\n'
1438
+ 'Export data to the file file.json::\n'
1439
+ '\n'
1440
+ ' $ rucio-admin data export file.json\n'
1441
+ '\n')
1442
+ export_parser.add_argument('file_path', action='store', help='File path.')
1443
+ export_parser.set_defaults(which='export')
1444
+
1445
+ # The account subparser
1446
+ account_parser = subparsers.add_parser('account', help='Account methods')
1447
+ account_subparser = account_parser.add_subparsers(dest='account_subcommand', **required_arg)
1448
+
1449
+ # The list_accounts command
1450
+ list_account_parser = account_subparser.add_parser('list',
1451
+ help='List Rucio accounts.',
1452
+ formatter_class=argparse.RawDescriptionHelpFormatter,
1453
+ epilog='Usage example\n'
1454
+ '"""""""""""""\n'
1455
+ '::\n'
1456
+ '\n'
1457
+ ' $ rucio-admin account list --type USER\n'
1458
+ '\n')
1459
+ list_account_parser.add_argument('--type', dest='account_type', action='store', help='Account Type (USER, GROUP, SERVICE)')
1460
+ list_account_parser.add_argument('--id', dest='identity', action='store', help='Identity (e.g. DN)')
1461
+ list_account_parser.add_argument('--filters', dest='filters', action='store', help='Filter arguments in form `key=value,another_key=next_value`')
1462
+ list_account_parser.add_argument("--csv", action='store_true', help='List result as a csv')
1463
+ list_account_parser.set_defaults(which='list_accounts')
1464
+
1465
+ # The list_account_attributes command
1466
+ list_attr_parser = account_subparser.add_parser('list-attributes',
1467
+ help='List attributes for an account.',
1468
+ formatter_class=argparse.RawDescriptionHelpFormatter,
1469
+ epilog='Usage example\n'
1470
+ '"""""""""""""\n'
1471
+ '::\n'
1472
+ '\n'
1473
+ ' $ rucio-admin account list-attributes jdoe\n'
1474
+ ' +-------+---------+\n'
1475
+ ' | Key | Value |\n'
1476
+ ' |-------+---------|\n'
1477
+ ' | admin | False |\n'
1478
+ ' +-------+---------+\n'
1479
+ '\n'
1480
+ 'Note: this table empty in most cases.\n'
1481
+ '\n')
1482
+ list_attr_parser.add_argument('account', action='store', help='Account name')
1483
+ list_attr_parser.set_defaults(which='list_account_attributes')
1484
+
1485
+ # The add_account_attribute command
1486
+ add_attr_parser = account_subparser.add_parser('add-attribute',
1487
+ help='Add attribute for an account.',
1488
+ formatter_class=argparse.RawDescriptionHelpFormatter,
1489
+ epilog='Usage example\n'
1490
+ '"""""""""""""\n'
1491
+ '::\n'
1492
+ '\n'
1493
+ ' $ rucio-admin account add-attribute --key \'test\' --value true jdoe\n'
1494
+ '\n'
1495
+ 'Note: no printed stdout.\n'
1496
+ '\n')
1497
+ add_attr_parser.add_argument('account', action='store', help='Account name')
1498
+ add_attr_parser.add_argument('--key', dest='key', action='store', help='Attribute key', required=True)
1499
+ add_attr_parser.add_argument('--value', dest='value', action='store', help='Attribute value', required=True)
1500
+ add_attr_parser.set_defaults(which='add_account_attribute')
1501
+
1502
+ # The delete_account_attribute command
1503
+ delete_attr_parser = account_subparser.add_parser('delete-attribute',
1504
+ help='Delete attribute for an account.',
1505
+ formatter_class=argparse.RawDescriptionHelpFormatter,
1506
+ epilog='Usage example\n'
1507
+ '"""""""""""""\n'
1508
+ '::\n'
1509
+ '\n'
1510
+ ' $ rucio-admin account delete-attribute --key \'test\' jdoe\n'
1511
+ '\n'
1512
+ 'Note: no printed stdout.\n'
1513
+ '\n')
1514
+ delete_attr_parser.add_argument('account', action='store', help='Account name')
1515
+ delete_attr_parser.add_argument('--key', dest='key', action='store', help='Attribute key', required=True)
1516
+ delete_attr_parser.set_defaults(which='delete_account_attribute')
1517
+
1518
+ # The add_account command
1519
+ add_account_parser = account_subparser.add_parser('add',
1520
+ help='Add Rucio account.',
1521
+ formatter_class=argparse.RawDescriptionHelpFormatter,
1522
+ epilog='Usage example\n'
1523
+ '"""""""""""""\n'
1524
+ '::\n'
1525
+ '\n'
1526
+ ' $ rucio-admin account add jdoe-sister\n'
1527
+ ' Added new account: jdoe-sister\n'
1528
+ '\n')
1529
+ add_account_parser.set_defaults(which='add_account')
1530
+ add_account_parser.add_argument('account', action='store', help='Account name')
1531
+ add_account_parser.add_argument('--type', dest='account_type', default='USER', help='Account Type (USER, GROUP, SERVICE)')
1532
+ add_account_parser.add_argument('--email', dest='email', action='store',
1533
+ help='Email address associated with the account')
1534
+
1535
+ # The disable_account command
1536
+ delete_account_parser = account_subparser.add_parser('delete',
1537
+ help='Delete Rucio account.',
1538
+ formatter_class=argparse.RawDescriptionHelpFormatter,
1539
+ epilog='Usage example\n'
1540
+ '"""""""""""""\n'
1541
+ '::\n'
1542
+ '\n'
1543
+ ' $ rucio-admin account delete jdoe-sister\n'
1544
+ ' Deleted account: jdoe-sister\n'
1545
+ '\n')
1546
+ delete_account_parser.set_defaults(which='delete_account')
1547
+ delete_account_parser.add_argument('account', action='store', help='Account name')
1548
+
1549
+ # The info_account command
1550
+ info_account_parser = account_subparser.add_parser('info',
1551
+ help='Show detailed information about an account.',
1552
+ formatter_class=argparse.RawDescriptionHelpFormatter,
1553
+ epilog='Usage example\n'
1554
+ '"""""""""""""\n'
1555
+ '::\n'
1556
+ '\n'
1557
+ ' $ rucio-admin account info jdoe\n'
1558
+ ' status : ACTIVE\n'
1559
+ ' account : jdoe\n'
1560
+ ' account_type : SERVICE\n'
1561
+ ' created_at : 2015-02-03T15:51:16\n'
1562
+ ' suspended_at : None\n'
1563
+ ' updated_at : 2015-02-03T15:51:16\n'
1564
+ ' deleted_at : None\n'
1565
+ ' email : None\n'
1566
+ '\n')
1567
+ info_account_parser.set_defaults(which='info_account')
1568
+ info_account_parser.add_argument('account', action='store', help='Account name')
1569
+
1570
+ # The list_account_identities command
1571
+ list_account_identities_parser = account_subparser.add_parser('list-identities',
1572
+ help='List all identities (DNs) on an account.',
1573
+ formatter_class=argparse.RawDescriptionHelpFormatter,
1574
+ epilog='Usage example\n'
1575
+ '"""""""""""""\n'
1576
+ '::\n'
1577
+ '\n'
1578
+ ' $ rucio-admin account list-identities jdoe\n'
1579
+ ' Identity: CN=Joe Doe,OU=Desy,O=GermanGrid,C=DE, type: X509\n'
1580
+ ' Identity: jdoe@CERN.CH, type: GSS\n'
1581
+ ' Identity: CN=Joe Doe,CN=707654,CN=jdoe,OU=Users,OU=Organic Units,DC=cern,DC=ch, type: X509\n'
1582
+ '\n')
1583
+ list_account_identities_parser.set_defaults(which='list_identities')
1584
+ list_account_identities_parser.add_argument('account', action='store', help='Account name')
1585
+
1586
+ # The set-limits command
1587
+ set_account_limits_parser = account_subparser.add_parser('set-limits',
1588
+ help='Set the limits for the provided account at given RSE.',
1589
+ formatter_class=argparse.RawDescriptionHelpFormatter,
1590
+ epilog='Usage example\n'
1591
+ '"""""""""""""\n'
1592
+ '::\n'
1593
+ '\n'
1594
+ ' $ rucio-admin account set-limits jdoe DESY-ZN_DATADISK 1000000000000\n'
1595
+ ' Set account limit for account jdoe on RSE DESY-ZN_DATADISK: 1.000 TB\n'
1596
+ '\n'
1597
+ 'Note: the order of perameters is fixed: account, rse, bytes.\n'
1598
+ '\n')
1599
+ set_account_limits_parser.set_defaults(which='set_limits')
1600
+ set_account_limits_parser.add_argument('account', action='store', help='Account name')
1601
+ set_account_limits_parser.add_argument('rse', action='store', help='RSE boolean expression')
1602
+ set_account_limits_parser.add_argument('bytes', action='store', help='Value can be specified in bytes ("10000"), with a storage unit ("10GB"), or "infinity"')
1603
+ set_account_limits_parser.add_argument('locality', action='store', nargs='?', default='local', choices=['local', 'global'], help='Global or local limit scope. Default: "local"')
1604
+
1605
+ # The account-limit subparser
1606
+ get_account_limits_parser = account_subparser.add_parser('get-limits',
1607
+ help='To get the account limits on an RSE.',
1608
+ formatter_class=argparse.RawDescriptionHelpFormatter,
1609
+ epilog='Usage example\n'
1610
+ '"""""""""""""\n'
1611
+ '::\n'
1612
+ '\n'
1613
+ ' $ rucio-admin account get-limits jdoe DESY-ZN_DATADISK\n'
1614
+ ' Quota on DESY-ZN_DATADISK for jdoe : 1.000 TB\n'
1615
+ 'Note: the order of parameters is fixed: account, rse.\n'
1616
+ '\n')
1617
+ get_account_limits_parser.set_defaults(which='get_limits')
1618
+ get_account_limits_parser.add_argument('account', action='store', help='Account name')
1619
+ get_account_limits_parser.add_argument('rse', action='store', help='The RSE name')
1620
+ get_account_limits_parser.add_argument('locality', action='store', nargs='?', default='local', choices=['local', 'global'], help='Global or local limit scope. Default: "local"')
1621
+
1622
+ # The delete_quota command
1623
+ delete_account_limits_parser = account_subparser.add_parser('delete-limits',
1624
+ help='Delete limites for an account at given RSE.',
1625
+ formatter_class=argparse.RawDescriptionHelpFormatter,
1626
+ epilog='Usage example\n'
1627
+ '"""""""""""""\n'
1628
+ '::\n'
1629
+ '\n'
1630
+ ' $ rucio-admain account delete-limits jdoe DESY-ZN_DATADISK\n'
1631
+ ' Deleted account limit for account jdoe and RSE DESY-ZN_DATADISK\n'
1632
+ '\n'
1633
+ 'Note: the order of parameters is fixed: account, rse.\n'
1634
+ '\n')
1635
+ delete_account_limits_parser.set_defaults(which='delete_limits')
1636
+ delete_account_limits_parser.add_argument('account', action='store', help='Account name')
1637
+ delete_account_limits_parser.add_argument('rse', action='store', help='RSE name')
1638
+ delete_account_limits_parser.add_argument('locality', action='store', nargs='?', default='local', choices=['local', 'global'], help='Global or local limit scope. Default: "local"')
1639
+
1640
+ # Ban/unban operations not implemented yet
1641
+ ban_account_limits_parser = account_subparser.add_parser('ban',
1642
+ help='Disable an account.',
1643
+ formatter_class=argparse.RawDescriptionHelpFormatter,
1644
+ epilog='Usage example\n'
1645
+ '"""""""""""""\n'
1646
+ '::\n'
1647
+ '\n'
1648
+ ' $ rucio-admin account ban --account jdoe\n'
1649
+ ' Account jdoe banned\n'
1650
+ '\n'
1651
+ 'Note: in case of accidental ban, use unban.\n'
1652
+ 'CAUTION: the account is completely disabled.\n'
1653
+ '\n')
1654
+ ban_account_limits_parser.set_defaults(which='ban_account')
1655
+ ban_account_limits_parser.add_argument('--account', dest='account', action='store', help='Account name', required=True)
1656
+
1657
+ unban_account_limits_parser = account_subparser.add_parser('unban',
1658
+ help='Unban a banned account. The account is mandatory parameter.',
1659
+ formatter_class=argparse.RawDescriptionHelpFormatter,
1660
+ epilog='Usage example\n'
1661
+ '"""""""""""""\n'
1662
+ '::\n'
1663
+ '\n'
1664
+ ' $ rucio-admin account unban --account jdoe\n'
1665
+ ' Account jdoe unbanned\n'
1666
+ '\n')
1667
+ unban_account_limits_parser.set_defaults(which='unban_account')
1668
+ unban_account_limits_parser.add_argument('--account', dest='account', action='store', help='Account name', required=True)
1669
+
1670
+ # Update account subparser
1671
+ update_account_parser = account_subparser.add_parser('update',
1672
+ help='Update an account.',
1673
+ formatter_class=argparse.RawDescriptionHelpFormatter,
1674
+ epilog='Usage example\n'
1675
+ '"""""""""""""\n'
1676
+ '::\n'
1677
+ '\n'
1678
+ ' $ rucio-admin account update --account jdoe --key email --value test\n'
1679
+ ' Account jdoe updated\n'
1680
+ '\n')
1681
+ update_account_parser.set_defaults(which='update_account')
1682
+ update_account_parser.add_argument('--account', dest='account', action='store', help='Account name', required=True)
1683
+ update_account_parser.add_argument('--key', dest='key', action='store', help='Account parameter', required=True)
1684
+ update_account_parser.add_argument('--value', dest='value', action='store', help='Account parameter value', required=True)
1685
+
1686
+ # The identity subparser
1687
+ identity_parser = subparsers.add_parser('identity', help='Identity methods')
1688
+ identity_subparser = identity_parser.add_subparsers(dest='identity_subcommand', **required_arg)
1689
+
1690
+ # The identity_add command
1691
+ identity_add_parser = identity_subparser.add_parser('add',
1692
+ help='Grant an identity access to an account.',
1693
+ formatter_class=argparse.RawDescriptionHelpFormatter,
1694
+ epilog='Usage example\n'
1695
+ '"""""""""""""\n'
1696
+ '\n'
1697
+ 'To add an identity of X509 type::\n'
1698
+ '\n'
1699
+ ' $ rucio-admin identity add --account jdoe --type X509 --id \'CN=Joe Doe,CN=707658,CN=jdoe,OU=Users,OU=Organic Units,DC=cern,DC=ch\' --email jdoe@cern.ch\n'
1700
+ ' Added new identity to account: CN=Joe Doe,CN=707658,CN=jdoe,OU=Users,OU=Organic Units,DC=cern,DC=ch\n'
1701
+ ' \n'
1702
+ ' $ rucio-admin account list-identities jdoe\n'
1703
+ ' Identity: CN=Joe Doe,CN=707658,CN=jdoe,OU=Users,OU=Organic Units,DC=cern,DC=ch, type: X509\n'
1704
+ '\n'
1705
+ 'Note: please keep the DN inside quota marks.\n'
1706
+ '\n'
1707
+ 'To add an identity of GSS type::\n'
1708
+ '\n'
1709
+ ' $ rucio-admin identity add --account jdoe --type GSS --email jdoe@cern.ch --id jdoe@CERN.CH\n'
1710
+ ' Added new identity to account: jdoe@CERN.CH-jdoe\n'
1711
+ ' \n'
1712
+ ' $ rucio-admin account list-identities jdoe\n'
1713
+ ' Identity: jdoe@CERN.CH, type: GSS\n'
1714
+ ' Identity: CN=Joe Doe,CN=707658,CN=jdoe,OU=Users,OU=Organic Units,DC=cern,DC=ch, type: X509\n'
1715
+ '\n')
1716
+ identity_add_parser.set_defaults(which='identity_add')
1717
+ identity_add_parser.add_argument('--account', dest='account', action='store', help='Account name', required=True)
1718
+ identity_add_parser.add_argument('--type', dest='authtype', action='store', choices=['X509', 'GSS', 'USERPASS', 'SSH', 'SAML', 'OIDC'], help='Authentication type [X509|GSS|USERPASS|SSH|SAML|OIDC]', required=True)
1719
+ identity_add_parser.add_argument('--id', dest='identity', action='store', help='Identity', required=True)
1720
+ identity_add_parser.add_argument('--email', dest='email', action='store', help='Email address associated with the identity', required=True)
1721
+ identity_add_parser.add_argument('--password', dest='password', action='store', help='Password if authtype is USERPASS', required=False)
1722
+
1723
+ # The identity_delete command
1724
+ identity_delete_parser = identity_subparser.add_parser('delete',
1725
+ help="Revoke an identity's access to an account. The mandatory parameters are account, type and identity.",
1726
+ formatter_class=argparse.RawDescriptionHelpFormatter,
1727
+ epilog='Usage example\n'
1728
+ '"""""""""""""\n'
1729
+ '::\n'
1730
+ '\n'
1731
+ ' $ rucio-admin identity delete --account jdoe --type X509 --id \'CN=Joe Doe,CN=707658,CN=jdoe,OU=Users,OU=Organic Units,DC=cern,DC=ch\'\n'
1732
+ ' Deleted identity: CN=Joe Doe,CN=707658,CN=jdoe,OU=Users,OU=Organic Units,DC=cern,DC=ch\n'
1733
+ '\n'
1734
+ 'Note: if the identity was accidentally deleted, use add option.\n'
1735
+ '\n')
1736
+ identity_delete_parser.set_defaults(which='identity_delete')
1737
+ identity_delete_parser.add_argument('--account', dest='account', action='store', help='Account name', required=True)
1738
+ identity_delete_parser.add_argument('--type', dest='authtype', action='store', choices=['X509', 'GSS', 'USERPASS', 'SSH', 'SAML', 'OIDC'], help='Authentication type [X509|GSS|USERPASS|SSH|SAML|OIDC]', required=True)
1739
+ identity_delete_parser.add_argument('--id', dest='identity', action='store', help='Identity', required=True)
1740
+
1741
+ # The RSE subparser
1742
+ rse_parser = subparsers.add_parser('rse', help='RSE (Rucio Storage Element) methods')
1743
+ rse_subparser = rse_parser.add_subparsers(dest='rse_subcommand', **required_arg)
1744
+
1745
+ # The list_rses command
1746
+ list_rse_parser = rse_subparser.add_parser('list',
1747
+ help='List all RSEs.',
1748
+ formatter_class=argparse.RawDescriptionHelpFormatter,
1749
+ epilog='Usage example\n'
1750
+ '"""""""""""""\n'
1751
+ 'To list all rses::\n'
1752
+ '\n'
1753
+ ' $ rucio-admin rse list'
1754
+ '\n'
1755
+ 'Note: same as rucio list-rses\n'
1756
+ '\n'
1757
+ 'To list special class of rses::\n'
1758
+ '\n'
1759
+ ' $ rucio list-rses --rses \"tier=2&type=DATADISK\"\n'
1760
+ '\n')
1761
+ list_rse_parser.add_argument("--csv", action='store_true', help='Output a list of RSEs as a csv')
1762
+ list_rse_parser.set_defaults(which='list_rses')
1763
+
1764
+ # The add_rse command
1765
+ add_rse_parser = rse_subparser.add_parser('add',
1766
+ help='Add new RSE.',
1767
+ formatter_class=argparse.RawDescriptionHelpFormatter,
1768
+ epilog='Example Usage\n'
1769
+ '"""""""""""""\n'
1770
+ '::\n'
1771
+ '\n'
1772
+ ' $ rucio-admin rse add JDOE_DATADISK\n'
1773
+ ' Added new deterministic RSE: JDOE_DATADISK\n'
1774
+ '\n'
1775
+ ' $ rucio-admin rse add --non-deterministic JDOE-TEST_DATATAPE\n'
1776
+ ' Added new non-deterministic RSE: JDOE-TEST_DATATAPE\n'
1777
+ '\n')
1778
+ add_rse_parser.set_defaults(which='add_rse')
1779
+ add_rse_parser.add_argument('rse', action='store', help='RSE name')
1780
+ add_rse_parser.add_argument('--non-deterministic', action='store_true', help='Create RSE in non-deterministic mode')
1781
+
1782
+ # The update_rse command
1783
+ update_rse_parser = rse_subparser.add_parser('update',
1784
+ help='Update RSE settings.',
1785
+ formatter_class=argparse.RawDescriptionHelpFormatter,
1786
+ epilog='Example Usage\n'
1787
+ '"""""""""""""\n'
1788
+ '\n'
1789
+ ' $ rucio-admin rse update --setting availability_write --value False\n'
1790
+ '\n'
1791
+ '\n')
1792
+ update_rse_parser.set_defaults(which='update_rse')
1793
+ update_rse_parser.add_argument('--rse', dest='rse', action='store', help='RSE name', required=True)
1794
+ update_rse_parser.add_argument('--setting', dest='param', action='store', help="One of deterministic, rse_type, staging_are, volatile, qos_class, availability_delete, availability_read, availability_write, city, country_name, latitude, longitude, region_code, time_zone", required=True) # noqa: E501
1795
+ update_rse_parser.add_argument('--value', dest='value', action='store', help='Value for the new setting configuration. Use "", None or null to wipe the value', required=True)
1796
+
1797
+ # The info_rse command
1798
+ info_rse_parser = rse_subparser.add_parser('info',
1799
+ help='Information about RSE.',
1800
+ formatter_class=argparse.RawDescriptionHelpFormatter,
1801
+ epilog='Usage example\n'
1802
+ '"""""""""""""\n'
1803
+ 'Information about a RSE::\n'
1804
+ '\n'
1805
+ ' $ rucio-admin rse info JDOE_DATADISK\n'
1806
+ ' Settings:\n'
1807
+ ' =========\n'
1808
+ ' third_party_copy_protocol: 1\n'
1809
+ ' rse_type: DISK\n'
1810
+ ' domain: [u\'lan\', u\'wan\']\n'
1811
+ ' availability_delete: True\n'
1812
+ ' delete_protocol: 1\n'
1813
+ ' rse: JDOE_DATADISK\n'
1814
+ ' deterministic: True\n'
1815
+ ' write_protocol: 1\n'
1816
+ ' read_protocol: 1\n'
1817
+ ' staging_area: False\n'
1818
+ ' credentials: None\n'
1819
+ ' availability_write: True\n'
1820
+ ' lfn2pfn_algorithm: default\n'
1821
+ ' availability_read: True\n'
1822
+ ' volatile: False\n'
1823
+ ' id: 9c54c73cbd534450b2202a576f809f1f\n'
1824
+ ' Attributes:\n'
1825
+ ' ===========\n'
1826
+ ' JDOE_DATADISK: True\n'
1827
+ ' Protocols:\n'
1828
+ ' ==========\n'
1829
+ ' Usage:\n'
1830
+ ' ======\n'
1831
+ ' rucio\n'
1832
+ ' used: 0\n'
1833
+ ' rse: JDOE_DATADISK\n'
1834
+ ' updated_at: 2018-02-16 13:08:28\n'
1835
+ ' free: None\n'
1836
+ ' source: rucio\n'
1837
+ ' total: 0\n'
1838
+ '\n'
1839
+ 'Note: alternatively: rucio list-rse-usage JDOE_DATADISK.\n'
1840
+ '\n')
1841
+ info_rse_parser.set_defaults(which='info_rse')
1842
+ info_rse_parser.add_argument('rse', action='store', help='RSE name')
1843
+
1844
+ # The set_attribute_rse command
1845
+ set_attribute_rse_parser = rse_subparser.add_parser('set-attribute',
1846
+ help='Add RSE attribute(key-value pair).',
1847
+ formatter_class=argparse.RawDescriptionHelpFormatter,
1848
+ epilog='Usage example\n'
1849
+ '"""""""""""""\n'
1850
+ '::\n'
1851
+ '\n'
1852
+ ' $ rucio-admin rse set-attribute --rse JDOE_DATADISK --key owner --value jdoe\n'
1853
+ ' Added new RSE attribute for JDOE_DATADISK: owner-jdoe\n'
1854
+ '\n'
1855
+ 'CAUTION: the existing attribute can be overwritten. Check rucio list-rse-attributes JDOE_DATADISK before setting an attribute.\n'
1856
+ '\n')
1857
+ set_attribute_rse_parser.set_defaults(which='set_attribute_rse')
1858
+ set_attribute_rse_parser.add_argument('--rse', dest='rse', action='store', help='RSE name', required=True)
1859
+ set_attribute_rse_parser.add_argument('--key', dest='key', action='store', help='Attribute key', required=True)
1860
+ set_attribute_rse_parser.add_argument('--value', dest='value', action='store', help='Attribute value', required=True)
1861
+
1862
+ # The delete_attribute_rse command
1863
+ delete_attribute_rse_parser = rse_subparser.add_parser('delete-attribute',
1864
+ help='Delete a RSE attribute(key-value pair).',
1865
+ formatter_class=argparse.RawDescriptionHelpFormatter,
1866
+ epilog='Usage example\n'
1867
+ '"""""""""""""\n'
1868
+ '::\n'
1869
+ '\n'
1870
+ ' $ rucio-admin rse delete-attribute --rse JDOE_DATADISK --key owner --value jdoe\n'
1871
+ ' Deleted RSE attribute for JDOE_DATADISK: owner-jdoe\n'
1872
+ '\n')
1873
+ delete_attribute_rse_parser.set_defaults(which='delete_attribute_rse')
1874
+ delete_attribute_rse_parser.add_argument('--rse', dest='rse', action='store', help='RSE name', required=True)
1875
+ delete_attribute_rse_parser.add_argument('--key', dest='key', action='store', help='Attribute key', required=True)
1876
+ delete_attribute_rse_parser.add_argument('--value', dest='value', action='store', help='Attribute value', required=True)
1877
+
1878
+ # The add_distance_rses command
1879
+ add_distance_rses_parser = rse_subparser.add_parser('add-distance',
1880
+ help='Set the distance between a pair of RSEs.',
1881
+ formatter_class=argparse.RawDescriptionHelpFormatter,
1882
+ epilog='Usage example\n'
1883
+ '"""""""""""""\n'
1884
+ '::\n'
1885
+ '\n'
1886
+ ' $ rucio-admin rse add-distance JDOE_SCRATCHDISK JDOE_DATADISK\n'
1887
+ ' Set distance from JDOE_SCRATCHDISK to JDOE_DATADISK to 1/n'
1888
+ '\n'
1889
+ 'Note::\n'
1890
+ '\n'
1891
+ ' --distance can be any positive integer, 0 is the closest\n'
1892
+ 'Note: order of RSEs is fixed: source, destination\n'
1893
+ '\n')
1894
+ add_distance_rses_parser.set_defaults(which='add_distance_rses')
1895
+ add_distance_rses_parser.add_argument(dest='source', action='store', help='Source RSE name')
1896
+ add_distance_rses_parser.add_argument(dest='destination', action='store', help='Destination RSE name')
1897
+ add_distance_rses_parser.add_argument('--distance', dest='distance', default=1, type=int, help='Distance between RSEs')
1898
+ add_distance_rses_parser.add_argument('--ranking', '--ranking', dest='ranking', new_option_string='--ranking', default=1, type=int, action=StoreAndDeprecateWarningAction, help='Ranking of link')
1899
+
1900
+ # The update_distance_rses command
1901
+ update_distance_rses_parser = rse_subparser.add_parser('update-distance',
1902
+ help='Update the existing distance between a pair of RSEs. The mandatory parameters are source, destination and distance or ranking.',
1903
+ formatter_class=argparse.RawDescriptionHelpFormatter,
1904
+ epilog='Usage example\n'
1905
+ '"""""""""""""\n'
1906
+ '::\n'
1907
+ '\n'
1908
+ ' $ rucio-admin rse update-distance JDOE_DATADISK JDOE_SCRATCHDISK --distance 10\n'
1909
+ ' Update distance information from JDOE_DATADISK to JDOE_SCRATCHDISK:\n'
1910
+ ' - Distance set to 10\n'
1911
+ '\n'
1912
+ 'Note::\n'
1913
+ '\n'
1914
+ ' --distance can be any positive integer, 0 is the closest\n'
1915
+ 'Note: order of RSEs is fixed: source, destination.\n'
1916
+ '\n')
1917
+ update_distance_rses_parser.set_defaults(which='update_distance_rses')
1918
+ update_distance_rses_parser.add_argument(dest='source', action='store', help='Source RSE name')
1919
+ update_distance_rses_parser.add_argument(dest='destination', action='store', help='Destination RSE name')
1920
+ update_distance_rses_parser.add_argument('--distance', dest='distance', type=int, help='Distance between RSEs')
1921
+ update_distance_rses_parser.add_argument('--ranking', '--ranking', dest='ranking', new_option_string='--ranking', type=int, action=StoreAndDeprecateWarningAction, help='Ranking of link')
1922
+
1923
+ # The delete_distance_rses command
1924
+ delete_distance_rses_parser = rse_subparser.add_parser('delete-distance',
1925
+ help='Delete the distance between a pair of RSEs. The mandatory parameters are source and destination.',
1926
+ formatter_class=argparse.RawDescriptionHelpFormatter,
1927
+ epilog='Usage example\n'
1928
+ '"""""""""""""\n'
1929
+ '::\n'
1930
+ '\n'
1931
+ ' $ rucio-admin rse delete-distance JDOE_DATADISK JDOE_SCRATCHDISK\n'
1932
+ ' Delete distance information from JDOE_DATADISK to JDOE_SCRATCHDISK:\n'
1933
+ '\n')
1934
+ delete_distance_rses_parser.set_defaults(which='delete_distance_rses')
1935
+ delete_distance_rses_parser.add_argument(dest='source', action='store', help='Source RSE name')
1936
+ delete_distance_rses_parser.add_argument(dest='destination', action='store', help='Destination RSE name')
1937
+
1938
+ # The get_distance_rses command
1939
+ get_distance_rses_parser = rse_subparser.add_parser('get-distance',
1940
+ help='Get the distance information between a pair of RSEs.',
1941
+ formatter_class=argparse.RawDescriptionHelpFormatter,
1942
+ epilog='Usage example\n'
1943
+ '"""""""""""""\n'
1944
+ '::\n'
1945
+ '\n'
1946
+ ' $ rucio-admin rse get-distance JDOE_DATADISK JDOE_SCRATCHDISK\n'
1947
+ ' Distance information from JDOE_DATADISK to JDOE_SCRATCHDISK: distance=3, ranking=10\n'
1948
+ '\n'
1949
+ 'Note: order of RSEs is fixed: source, destination.\n'
1950
+ '\n')
1951
+ get_distance_rses_parser.set_defaults(which='get_distance_rses')
1952
+ get_distance_rses_parser.add_argument(dest='source', action='store', help='Source RSE name')
1953
+ get_distance_rses_parser.add_argument(dest='destination', action='store', help='Destination RSE name')
1954
+
1955
+ # The get_attribute_rse command
1956
+ get_attribute_rse_parser = rse_subparser.add_parser('get-attribute',
1957
+ help='List RSE attributes.',
1958
+ formatter_class=argparse.RawDescriptionHelpFormatter,
1959
+ epilog='Usage example\n'
1960
+ '"""""""""""""\n'
1961
+ '::\n'
1962
+ '\n'
1963
+ ' $ rucio-admin rse get-attribute JDOE_DATADISK\n'
1964
+ ' owner: jdoe\n'
1965
+ ' JDOE_DATADISK: True\n'
1966
+ '\n'
1967
+ 'Note: alternatively: rucio list-rse-attributes JDOE_DATADISK.\n'
1968
+ '\n')
1969
+ get_attribute_rse_parser.set_defaults(which='get_attribute_rse')
1970
+ get_attribute_rse_parser.add_argument(dest='rse', action='store', help='RSE name')
1971
+
1972
+ # The add_protocol_rse command
1973
+ add_protocol_rse_parser = rse_subparser.add_parser('add-protocol',
1974
+ help='Add a protocol and its settings to a RSE.',
1975
+ formatter_class=argparse.RawDescriptionHelpFormatter,
1976
+ epilog='Usage example\n'
1977
+ '"""""""""""""\n'
1978
+ '::\n'
1979
+ '\n'
1980
+ ' $ rucio-admin rse add-protocol --hostname jdoes.test.org --scheme gsiftp --prefix \'/atlasdatadisk/rucio/\' --port 8443 JDOE_DATADISK\n'
1981
+ '\n'
1982
+ 'Note: no printed stdout.\n'
1983
+ 'Note: examples of optional parameters::\n'
1984
+ '\n'
1985
+ ' --space-token DATADISK\n'
1986
+ ' --web-service-path \'/srm/managerv2?SFN=\'\n'
1987
+ ' --port 8443\n'
1988
+ ' --impl \'rucio.rse.protocols.gfal.Default\'\n'
1989
+ ' (for other protocol implementation, replace gfal2 with impl. name, e.g. srm)\n'
1990
+ ' --domain-json\n'
1991
+ ' --extended-attributes-json example.json\n'
1992
+ ' where example.json contains dict {\'attr_name\':\'value\', ...}\n'
1993
+ '\n')
1994
+ add_protocol_rse_parser.set_defaults(which='add_protocol_rse')
1995
+ add_protocol_rse_parser.add_argument(dest='rse', action='store', help='RSE name')
1996
+ add_protocol_rse_parser.add_argument('--hostname', dest='hostname', action='store', help='Endpoint hostname', required=True)
1997
+ add_protocol_rse_parser.add_argument('--scheme', dest='scheme', action='store', help='Endpoint URL scheme', required=True)
1998
+ add_protocol_rse_parser.add_argument('--prefix', dest='prefix', action='store', help='Endpoint URL path prefix', required=True)
1999
+ add_protocol_rse_parser.add_argument('--space-token', dest='space_token', action='store', help='Space token name (SRM-only)')
2000
+ add_protocol_rse_parser.add_argument('--web-service-path', dest='web_service_path', action='store', help='Web service URL (SRM-only)')
2001
+ add_protocol_rse_parser.add_argument('--port', dest='port', action='store', type=int, help='URL port')
2002
+ add_protocol_rse_parser.add_argument('--impl', dest='impl', default='rucio.rse.protocols.gfal.Default', action='store', help='Transfer protocol implementation to use')
2003
+ add_protocol_rse_parser.add_argument('--domain-json', dest='domain_json', action='store', type=json.loads, help='JSON describing the WAN / LAN setup')
2004
+ add_protocol_rse_parser.add_argument('--extended-attributes-json', dest='ext_attr_json', action='store', type=json.loads, help='JSON describing any extended attributes')
2005
+
2006
+ # The del_protocol_rse command
2007
+ del_protocol_rse_parser = rse_subparser.add_parser('delete-protocol',
2008
+ help='Delete a protocol from a RSE.',
2009
+ formatter_class=argparse.RawDescriptionHelpFormatter,
2010
+ epilog='Usage example\n'
2011
+ '"""""""""""""\n'
2012
+ '::\n'
2013
+ '\n'
2014
+ ' $ rucio-admin rse delete-protocol --scheme gsiftp JDOE_DATADISK\n'
2015
+ '\n'
2016
+ 'Note: no printed stdout.\n'
2017
+ '\n')
2018
+ del_protocol_rse_parser.set_defaults(which='del_protocol_rse')
2019
+ del_protocol_rse_parser.add_argument(dest='rse', action='store', help='RSE name')
2020
+ del_protocol_rse_parser.add_argument('--hostname', dest='hostname', action='store', help='Endpoint hostname')
2021
+ del_protocol_rse_parser.add_argument('--scheme', dest='scheme', action='store', help='Endpoint URL scheme', required=True)
2022
+ del_protocol_rse_parser.add_argument('--port', dest='port', action='store', type=int, help='URL port')
2023
+
2024
+ # The disable_location command
2025
+ disable_rse_parser = rse_subparser.add_parser('delete',
2026
+ help='Disable RSE.',
2027
+ formatter_class=argparse.RawDescriptionHelpFormatter,
2028
+ epilog='Usage example\n'
2029
+ '"""""""""""""\n'
2030
+ '::\n'
2031
+ '\n'
2032
+ ' $ rucio-admin rse delete JDOE_SCRATCHDISK\n'
2033
+ '\n'
2034
+ 'Note: no printed stdout.\n'
2035
+ 'CAUTION: all information about the RSE might be lost!\n'
2036
+ '\n')
2037
+ disable_rse_parser.set_defaults(which='disable_rse')
2038
+ disable_rse_parser.add_argument('rse', action='store', help='RSE name')
2039
+
2040
+ # The add_qos_policy command
2041
+ add_qos_policy_parser = rse_subparser.add_parser('add-qos-policy',
2042
+ help='Add a QoS policy to an RSE.',
2043
+ formatter_class=argparse.RawDescriptionHelpFormatter,
2044
+ epilog='Usage example\n'
2045
+ '"""""""""""""\n'
2046
+ '\n'
2047
+ ' $ rucio-admin rse add-qos-policy JDOE_DATADISK SLOW_BUT_CHEAP')
2048
+ add_qos_policy_parser.set_defaults(which='add_qos_policy')
2049
+ add_qos_policy_parser.add_argument('rse', action='store', help='RSE name')
2050
+ add_qos_policy_parser.add_argument('qos_policy', action='store', help='QoS policy')
2051
+
2052
+ # The delete_qos_policy command
2053
+ delete_qos_policy_parser = rse_subparser.add_parser('delete-qos-policy',
2054
+ help='Delete a QoS policy from an RSE.',
2055
+ formatter_class=argparse.RawDescriptionHelpFormatter,
2056
+ epilog='Usage example\n'
2057
+ '"""""""""""""\n'
2058
+ '\n'
2059
+ ' $ rucio-admin rse delete-qos-policy JDOE_DATADISK SLOW_BUT_CHEAP')
2060
+ delete_qos_policy_parser.set_defaults(which='delete_qos_policy')
2061
+ delete_qos_policy_parser.add_argument('rse', action='store', help='RSE name')
2062
+ delete_qos_policy_parser.add_argument('qos_policy', action='store', help='QoS policy')
2063
+
2064
+ # The delete_qos_policy command
2065
+ list_qos_policies_parser = rse_subparser.add_parser('list-qos-policies',
2066
+ help='List all QoS policies of an RSE.',
2067
+ formatter_class=argparse.RawDescriptionHelpFormatter,
2068
+ epilog='Usage example\n'
2069
+ '"""""""""""""\n'
2070
+ '\n'
2071
+ ' $ rucio-admin rse list-qos-policies JDOE_DATADISK')
2072
+ list_qos_policies_parser.set_defaults(which='list_qos_policies')
2073
+ list_qos_policies_parser.add_argument('rse', action='store', help='RSE name')
2074
+
2075
+ # The set_limit_rse command
2076
+ set_limit_rse_parser = rse_subparser.add_parser('set-limit',
2077
+ help='Set a RSE limit',
2078
+ formatter_class=argparse.RawDescriptionHelpFormatter,
2079
+ epilog='Usage example\n'
2080
+ '"""""""""""""\n'
2081
+ '\n'
2082
+ ' $ rucio-admin rse set-limit XRD1 MinFreeSpace 10000')
2083
+ set_limit_rse_parser.set_defaults(which='set_limit_rse')
2084
+ set_limit_rse_parser.add_argument('rse', action='store', help='RSE name')
2085
+ set_limit_rse_parser.add_argument('name', action='store', help='Name of the limit')
2086
+ set_limit_rse_parser.add_argument('value', action='store', help='Value of the limit')
2087
+
2088
+ # The delete_limit_rse command
2089
+ set_limit_rse_parser = rse_subparser.add_parser('delete-limit',
2090
+ help='Delete a RSE limit',
2091
+ formatter_class=argparse.RawDescriptionHelpFormatter,
2092
+ epilog='Usage example\n'
2093
+ '"""""""""""""\n'
2094
+ '\n'
2095
+ ' $ rucio-admin rse delete-limit XRD3 MinFreeSpace')
2096
+ set_limit_rse_parser.set_defaults(which='delete_limit_rse')
2097
+ set_limit_rse_parser.add_argument('rse', action='store', help='RSE name')
2098
+ set_limit_rse_parser.add_argument('name', action='store', help='Name of the limit')
2099
+
2100
+ # The scope subparser
2101
+ scope_parser = subparsers.add_parser('scope', help='Scope methods')
2102
+ scope_subparser = scope_parser.add_subparsers(dest='scope_subcommand', **required_arg)
2103
+
2104
+ # The add_scope command
2105
+ add_scope_parser = scope_subparser.add_parser('add',
2106
+ help='Add scope.',
2107
+ formatter_class=argparse.RawDescriptionHelpFormatter,
2108
+ epilog='Usage example\n'
2109
+ '"""""""""""""\n'
2110
+ '::\n'
2111
+ '\n'
2112
+ ' $ rucio-admin scope add --scope user.jdoe --account jdoe\n'
2113
+ ' Added new scope to account: user.jdoe-jdoe\n'
2114
+ '\n')
2115
+ add_scope_parser.set_defaults(which='add_scope')
2116
+ add_scope_parser.add_argument('--account', dest='account', action='store', help='Account name', required=True)
2117
+ add_scope_parser.add_argument('--scope', dest='scope', action='store', help='Scope name', required=True)
2118
+
2119
+ # The list_scope command
2120
+ list_scope_parser = scope_subparser.add_parser('list',
2121
+ help='List scopes.',
2122
+ formatter_class=argparse.RawDescriptionHelpFormatter,
2123
+ epilog='Usage example\n'
2124
+ '"""""""""""""\n'
2125
+ '::\n'
2126
+ '\n'
2127
+ ' $ rucio-admin scope list --account jdoe\n'
2128
+ ' user.jdoe\n'
2129
+ '\n'
2130
+ 'Note: alternatively: rucio list-scopes.\n'
2131
+ '\n')
2132
+ list_scope_parser.set_defaults(which='list_scopes')
2133
+ list_scope_parser.add_argument('--account', dest='account', action='store', help='Account name')
2134
+
2135
+ # The config subparser
2136
+ config_parser = subparsers.add_parser('config',
2137
+ help='Configuration methods. The global configuration of data management system can by modified.',
2138
+ formatter_class=argparse.RawDescriptionHelpFormatter,
2139
+ epilog='''e.g. quotas, daemons, rses''')
2140
+ config_subparser = config_parser.add_subparsers(dest='config_subcommand', **required_arg)
2141
+
2142
+ # The get_config command
2143
+ get_config_parser = config_subparser.add_parser('get',
2144
+ help='Get matching configuration.',
2145
+ formatter_class=argparse.RawDescriptionHelpFormatter,
2146
+ epilog='Usage example\n'
2147
+ '"""""""""""""\n'
2148
+ '::\n'
2149
+ '\n'
2150
+ ' $ rucio-admin config get --section quota\n'
2151
+ ' [quota]\n'
2152
+ ' LOCALGROUPDISK=95\n'
2153
+ ' SCRATCHDISK=30\n'
2154
+ ' USERDISK=30\n'
2155
+ '\n'
2156
+ 'Note: to list other sections: rucio-admin config get.\n'
2157
+ '\n')
2158
+ get_config_parser.set_defaults(which='get_config')
2159
+ get_config_parser.add_argument('--section', dest='section', action='store', help='Section name', required=False)
2160
+ get_config_parser.add_argument('--option', dest='option', action='store', help='Option name', required=False)
2161
+
2162
+ # The set_config_option command
2163
+ set_config_parser = config_subparser.add_parser('set',
2164
+ help='Set matching configuration.',
2165
+ formatter_class=argparse.RawDescriptionHelpFormatter,
2166
+ epilog='Usage example\n'
2167
+ '"""""""""""""\n'
2168
+ '::\n'
2169
+ '\n'
2170
+ ' $ rucio-admin config set --section limitsscratchdisk --option testlimit --value 30\n'
2171
+ ' Set configuration: limitsscratchdisk.testlimit=30\n'
2172
+ '\n'
2173
+ 'CAUTION: you might not intend to change global configuration!\n'
2174
+ '\n')
2175
+ set_config_parser.set_defaults(which='set_config_option')
2176
+ set_config_parser.add_argument('--section', dest='section', action='store', help='Section name', required=True)
2177
+ set_config_parser.add_argument('--option', dest='option', action='store', help='Option name', required=True)
2178
+ set_config_parser.add_argument('--value', dest='value', action='store', help='String-encoded value', required=True)
2179
+
2180
+ # The delete_config_option command
2181
+ delete_config_parser = config_subparser.add_parser('delete',
2182
+ help='Delete matching configuration.',
2183
+ formatter_class=argparse.RawDescriptionHelpFormatter,
2184
+ epilog='Usage example\n'
2185
+ '"""""""""""""\n'
2186
+ '::\n'
2187
+ '\n'
2188
+ ' $ rucio-admin config delete --section limitsscratchdisk --option testlimit\n'
2189
+ ' Deleted section \'limitsscratchdisk\' option \'testlimit\'\n'
2190
+ '\n'
2191
+ 'CAUTION: you might not intend to change global configuration!\n'
2192
+ '\n')
2193
+ delete_config_parser.set_defaults(which='delete_config_option')
2194
+ delete_config_parser.add_argument('--section', dest='section', action='store', help='Section name', required=True)
2195
+ delete_config_parser.add_argument('--option', dest='option', action='store', help='Option name', required=True)
2196
+
2197
+ # The subscription parser
2198
+ subs_parser = subparsers.add_parser('subscription', help='Subscription methods. The methods for automated and regular processing of some specific rules.')
2199
+ subs_subparser = subs_parser.add_subparsers(dest='subscription_subcommand', **required_arg)
2200
+
2201
+ # The add-subscription command
2202
+ add_sub_parser = subs_subparser.add_parser('add',
2203
+ help='Add subscription',
2204
+ formatter_class=argparse.RawDescriptionHelpFormatter,
2205
+ epilog='Usage example\n'
2206
+ '"""""""""""""\n'
2207
+ '::\n'
2208
+ '\n'
2209
+ ' $ rucio-admin subscription add --lifetime 2 --account jdoe --priority 1 jdoes_txt_files_on_datadisk\n'
2210
+ ' \'{\"scope\": [\"user.jdoe\"], \"datatype\": [\"txt\"]}\' \'[{\"copies\": 1, \"rse_expression\": \"JDOE_DATADISK\", \"lifetime\": 3600, \"activity\": \"User Subscriptions\"}]\'\n'
2211
+ ' \'keeping replica on jdoes disk for 60 mins\'\n'
2212
+ ' Subscription added 9a89cc8e692f4cabb8836fdafd884c5a\n'
2213
+ '\n'
2214
+ 'Note: priority can range from 1 to infinity. Internal share for given account.\n'
2215
+ '\n')
2216
+ add_sub_parser.set_defaults(which='add_subscription')
2217
+ add_sub_parser.add_argument(dest='name', action='store', help='Subscription name')
2218
+ add_sub_parser.add_argument(dest='filter', action='store', help='DID filter (eg \'{"scope": ["tests"], "project": ["data12_8TeV"]}\')')
2219
+ add_sub_parser.add_argument(dest='replication_rules', action='store', help='Replication rules (eg \'[{"copies": 2, "rse_expression": "tier=2", "lifetime": 3600, "activity": "Functional Tests", "weight": "mou"}]\')')
2220
+ add_sub_parser.add_argument(dest='comments', action='store', help='Comments on subscription')
2221
+ add_sub_parser.add_argument('--lifetime', dest='lifetime', action='store', type=int, help='Subscription lifetime (in days)')
2222
+ add_sub_parser.add_argument('--account', dest='subs_account', action='store', help='Account name')
2223
+ add_sub_parser.add_argument('--priority', dest='priority', action='store', help='The priority of the subscription')
2224
+ # retroactive and dry_run hard-coded for now
2225
+
2226
+ # The list-subscriptions command
2227
+ list_sub_parser = subs_subparser.add_parser('list',
2228
+ help='List subscriptions',
2229
+ formatter_class=argparse.RawDescriptionHelpFormatter,
2230
+ epilog='Usage example\n'
2231
+ '"""""""""""""\n'
2232
+ '::\n'
2233
+ '\n'
2234
+ ' $ rucio-admin subscription list --account jdoe\n'
2235
+ ' jdoe: jdoes_txt_files_on_datadisk UPDATED\n'
2236
+ ' priority: 1\n'
2237
+ ' filter: {\'datatype\': [\'txt\'], \'scope\': [\'user.jdoe\']}\n'
2238
+ ' rules: [{\'lifetime\': 3600, \'rse_expression\': \'JDOE_DATADISK\', \'copies\': 1, \'activity\': \'User Subscriptions\'}]\n'
2239
+ ' comments: keeping replica on jdoes disk for 60 mins\n'
2240
+ '\n')
2241
+ list_sub_parser.set_defaults(which='list_subscriptions')
2242
+ list_sub_parser.add_argument('--account', dest='subs_account', action='store', help='Account name')
2243
+ list_sub_parser.add_argument('--long', dest='long', action='store_true', help='Long listing')
2244
+ list_sub_parser.add_argument(dest='name', nargs='?', action='store', help='Subscription name')
2245
+
2246
+ # The update-subscription command
2247
+ update_sub_parser = subs_subparser.add_parser('update',
2248
+ help='Update subscription',
2249
+ formatter_class=argparse.RawDescriptionHelpFormatter,
2250
+ epilog='Usage example\n'
2251
+ '"""""""""""""\n'
2252
+ '::\n'
2253
+ '\n'
2254
+ ' $ rucio-admin subscription update --lifetime 3 --account jdoe --priority 1 jdoes_txt_files_on_datadisk\n'
2255
+ ' \'{\"scope\": [\"user.jdoe\"], \"datatype\": [\"txt\"]}\' \'[{\"copies\": 1, \"rse_expression\": \"JDOE_DATADISK\", \"lifetime\": 3600, \"activity\": \"User Subscriptions\"}]\n'
2256
+ ' keeping replica on jdoes disk for 60 mins, valid until 23.2.2018\n'
2257
+ '\n'
2258
+ 'Note: no printed stdout.\n'
2259
+ 'Note: all the input parameters are mandatory.\n'
2260
+ '::\n'
2261
+ '\n'
2262
+ ' $ rucio-admin subscription list --account jdoe\n'
2263
+ ' jdoe: jdoes_txt_files_on_datadisk UPDATED\n'
2264
+ ' priority: 1\n'
2265
+ ' filter: {\"datatype\": [\"txt\"], \"scope\": [\"user.jdoe\"]}\n'
2266
+ ' rules: [{\"lifetime\": 3600, \"rse_expression\": \"JDOE_DATADISK\", \"copies\": 1, \"activity\": \"User Subscriptions\"}]\n'
2267
+ ' comments: keeping replica on jdoes disk for 60 mins, valid until 23.2.2018\n'
2268
+ '\n')
2269
+ update_sub_parser.set_defaults(which='update_subscription')
2270
+ update_sub_parser.add_argument(dest='name', action='store', help='Subscription name')
2271
+ update_sub_parser.add_argument(dest='filter', action='store', help='DID filter (eg \'{"scope": ["tests"], "project": ["data12_8TeV"]}\')')
2272
+ update_sub_parser.add_argument(dest='replication_rules', action='store', help='Replication rules (eg \'[{"activity": "Functional Tests", "copies": 2, "rse_expression": "tier=2", "lifetime": 3600, "weight": "mou"}]\')')
2273
+ update_sub_parser.add_argument(dest='comments', action='store', help='Comments on subscription')
2274
+ update_sub_parser.add_argument('--lifetime', dest='lifetime', action='store', type=int, help='Subscription lifetime (in days)')
2275
+ update_sub_parser.add_argument('--account', dest='subs_account', action='store', help='Account name')
2276
+ update_sub_parser.add_argument('--priority', dest='priority', action='store', help='The priority of the subscription')
2277
+ # subscription policy, retroactive and dry_run hard-coded for now
2278
+
2279
+ # The reevaluate command
2280
+ reevaluate_did_for_subscription_parser = subs_subparser.add_parser('reevaluate',
2281
+ help='Reevaluate a list of DIDs against all active subscriptions',
2282
+ formatter_class=argparse.RawDescriptionHelpFormatter,
2283
+ epilog='Usage example\n'
2284
+ '"""""""""""""\n'
2285
+ '::\n'
2286
+ '\n'
2287
+ ' $ rucio-admin subscription reevaluate user.jdoe:jdoes.test.dataset\n'
2288
+ '\n'
2289
+ 'Note: no printed stdout.\n'
2290
+ '\n')
2291
+ reevaluate_did_for_subscription_parser.set_defaults(which='reevaluate_did_for_subscription')
2292
+ reevaluate_did_for_subscription_parser.add_argument(dest='dids', action='store', help='List of DIDs (coma separated)')
2293
+
2294
+ # The replica parser
2295
+ rep_parser = subparsers.add_parser('replicas', help='Replica methods')
2296
+ rep_subparser = rep_parser.add_subparsers(dest='replicas_subcommand', **required_arg)
2297
+
2298
+ # The add-quarantined command
2299
+ quarantine_parser = rep_subparser.add_parser('quarantine', help="Add quarantined replicas",
2300
+ formatter_class=argparse.RawDescriptionHelpFormatter,
2301
+ epilog=dedent("""\
2302
+ Usage example
2303
+ =============
2304
+ ::
2305
+ $ cat replica_list.txt
2306
+ /path/to/file_1.data
2307
+ /path/to/another/file.data
2308
+ $ rucio admin replicas quarantine --rse STORAGE --paths replica_list.txt
2309
+
2310
+ $ rucio admin replicas quarantine --rse STORAGE /path/to/a/data_file /path/to/some/other/file
2311
+ """))
2312
+ quarantine_parser.set_defaults(which='quarantine_replicas')
2313
+ quarantine_parser.add_argument("--paths", dest="paths_file", action="store", help="A file with replica paths, one path per line")
2314
+ quarantine_parser.add_argument("--rse", dest="rse", action="store", help="RSE name")
2315
+ quarantine_parser.add_argument(dest='paths_list', action='store', nargs='*', help='List of replica paths')
2316
+
2317
+ # The declare-bad command
2318
+ declare_bad_file_replicas_parser = rep_subparser.add_parser('declare-bad',
2319
+ help='Declare bad file replicas',
2320
+ formatter_class=argparse.RawDescriptionHelpFormatter,
2321
+ epilog='Usage example\n'
2322
+ '"""""""""""""\n'
2323
+ '::\n'
2324
+ '\n'
2325
+ ' $ rucio-admin replicas declare-bad\n'
2326
+ ' srm://se.bfg.uni-freiburg.de:8443/srm/managerv2?SFN=/pnfs/bfg.uni-freiburg.de/data/atlasdatadisk/rucio/user/jdoe/e2/a7/jdoe.TXT.txt --reason \'test only\'\n'
2327
+ '\n'
2328
+ 'Note: no printed stdout.\n'
2329
+ '\n'
2330
+ 'Note: pfn can be provided, see rucio-admin replicas list-pfns or rucio list-file-replicas\n'
2331
+ '\n')
2332
+ declare_bad_file_replicas_parser.set_defaults(which='declare_bad_file_replicas')
2333
+ declare_bad_file_replicas_parser.add_argument(dest='listbadfiles', action='store', nargs='*', help='List of bad items. Each can be a PFN (for one replica) or an LFN (for all replicas of the LFN) or a collection DID (for all file replicas in the DID)')
2334
+ declare_bad_file_replicas_parser.add_argument('--reason', dest='reason', required=True, action='store', help='Reason')
2335
+ declare_bad_file_replicas_parser.add_argument('--inputfile', dest='inputfile', nargs='?', action='store', help='File containing list of bad items')
2336
+ declare_bad_file_replicas_parser.add_argument('--allow-collection', dest='allow_collection', action='store_true', help='Allow passing a collection DID as bad item')
2337
+
2338
+ declare_bad_file_replicas_parser.add_argument('--lfns', dest='lfns', nargs='?', action='store', help='File containing list of LFNs for bad replicas. Requires --rse and --scope')
2339
+ declare_bad_file_replicas_parser.add_argument('--scope', dest='scope', nargs='?', action='store', help='Common scope for bad replicas specified with LFN list, ignored without --lfns')
2340
+ declare_bad_file_replicas_parser.add_argument('--rse', dest='rse', nargs='?', action='store', help='Common RSE for bad replicas specified with LFN list, ignored without --lfns')
2341
+
2342
+ # The declare-temporary-unavailable command
2343
+ declare_temporary_unavailable_replicas_parser = rep_subparser.add_parser('declare-temporary-unavailable',
2344
+ help='Declare temporary unavailable replicas',
2345
+ formatter_class=argparse.RawDescriptionHelpFormatter,
2346
+ epilog='Usage example\n'
2347
+ '"""""""""""""\n'
2348
+ '::\n'
2349
+ '\n'
2350
+ ' $ rucio-admin replicas declare-temporary-unavailable\n'
2351
+ ' srm://se.bfg.uni-freiburg.de/pnfs/bfg.uni-freiburg.de/data/atlasdatadisk/rucio/user/jdoe/e2/a7/jdoe.TXT.txt --duration 3600 --reason \'test only\'\n')
2352
+ declare_temporary_unavailable_replicas_parser.set_defaults(which='declare_temporary_unavailable_replicas')
2353
+ declare_temporary_unavailable_replicas_parser.add_argument(dest='listbadfiles', action='store', nargs='*', help='List of replicas. Each needs to be a proper PFN including the protocol')
2354
+ declare_temporary_unavailable_replicas_parser.add_argument('--reason', dest='reason', required=True, action='store', help='Reason')
2355
+ declare_temporary_unavailable_replicas_parser.add_argument('--inputfile', dest='inputfile', nargs='?', action='store', help='File containing list of replicas')
2356
+ declare_temporary_unavailable_replicas_parser.add_argument('--expiration-date', '--duration', new_option_string='--duration', dest='duration', required=True, action=StoreAndDeprecateWarningAction, type=int, help='Timeout in seconds when the replicas will become available again.') # NOQA: E501
2357
+
2358
+ # The list-pfns command
2359
+ list_pfns_parser = rep_subparser.add_parser('list-pfns',
2360
+ help='List the possible PFN for a file at a site.',
2361
+ formatter_class=argparse.RawDescriptionHelpFormatter,
2362
+ epilog='Usage example\n'
2363
+ '"""""""""""""\n'
2364
+ '::\n'
2365
+ '\n'
2366
+ ' $ rucio-admin replicas list-pfns \n'
2367
+ ' user.jdoe:jdoe.TXT.txt CERN-PROD_SCRATCHDISK srm \'{\"all_states\": False, \"schemes\": [\"srm\"], \"dids\": [{\"scope\": \"user.jdoe\", \"name\": \"jdoe.TXT.txt\"}]}\'\n'
2368
+ ' srm://srm-eosatlas.cern.ch:8443/srm/v2/server?SFN=/eos/atlas/atlasscratchdisk/rucio/user/jdoe/e2/a7/jdoe.TXT.txt'
2369
+ '\n')
2370
+ list_pfns_parser.set_defaults(which='list_pfns')
2371
+ list_pfns_parser.add_argument(dest='dids', action='store', help='List of DIDs (coma separated)')
2372
+ list_pfns_parser.add_argument(dest='rse', action='store', help='RSE')
2373
+ list_pfns_parser.add_argument(dest='protocol', action='store', default='srm', help='The protocol, by default srm, can be one of [root|srm|http(s)].')
2374
+
2375
+ # The set-tombstone command
2376
+ set_tombstone_parser = rep_subparser.add_parser('set-tombstone',
2377
+ help='Set a tombstone on a replica manually to force deletion. Only works if there is no lock on the replica.',
2378
+ formatter_class=argparse.RawDescriptionHelpFormatter,
2379
+ epilog='Usage example\n'
2380
+ '"""""""""""""\n'
2381
+ '::\n'
2382
+ '\n'
2383
+ ' $ rucio-admin replicas set-tombstone mock:file --rse MOCK'
2384
+ '\n')
2385
+ set_tombstone_parser.add_argument('dids', action='store', help='One or multiple comma separated DIDs.')
2386
+ set_tombstone_parser.add_argument('--rse', action='store', required=True, help='RSE')
2387
+ set_tombstone_parser.set_defaults(which='set_tombstone')
2388
+
2389
+ return oparser
2390
+
2391
+
2392
+ def main():
2393
+ oparser = get_parser()
2394
+ if EXTRA_MODULES['argcomplete']:
2395
+ argcomplete.autocomplete(oparser)
2396
+
2397
+ if len(sys.argv) == 1:
2398
+ oparser.print_help()
2399
+ sys.exit(FAILURE)
2400
+
2401
+ args = oparser.parse_args()
2402
+
2403
+ if not hasattr(args, 'which'):
2404
+ oparser.print_help()
2405
+ sys.exit(FAILURE)
2406
+ else:
2407
+ commands = {'add_account': add_account,
2408
+ 'list_accounts': list_accounts,
2409
+ 'list_account_attributes': list_account_attributes,
2410
+ 'add_account_attribute': add_account_attribute,
2411
+ 'delete_account_attribute': delete_account_attribute,
2412
+ 'delete_account': delete_account,
2413
+ 'info_account': info_account,
2414
+ 'ban_account': ban_account,
2415
+ 'unban_account': unban_account,
2416
+ 'update_account': update_account,
2417
+ 'get_limits': get_limits,
2418
+ 'set_limits': set_limits,
2419
+ 'delete_limits': delete_limits,
2420
+ 'list_identities': list_identities,
2421
+ 'identity_add': identity_add,
2422
+ 'identity_delete': identity_delete,
2423
+ 'add_rse': add_rse,
2424
+ 'update_rse': update_rse,
2425
+ 'set_attribute_rse': set_attribute_rse,
2426
+ 'get_attribute_rse': get_attribute_rse,
2427
+ 'delete_attribute_rse': delete_attribute_rse,
2428
+ 'add_distance_rses': add_distance_rses,
2429
+ 'update_distance_rses': update_distance_rses,
2430
+ 'get_distance_rses': get_distance_rses,
2431
+ 'delete_distance_rses': delete_distance_rses,
2432
+ 'add_protocol_rse': add_protocol_rse,
2433
+ 'del_protocol_rse': del_protocol_rse,
2434
+ 'list_rses': list_rses,
2435
+ 'disable_rse': disable_rse,
2436
+ 'add_qos_policy': add_qos_policy,
2437
+ 'delete_qos_policy': delete_qos_policy,
2438
+ 'list_qos_policies': list_qos_policies,
2439
+ 'add_scope': add_scope,
2440
+ 'list_scopes': list_scopes,
2441
+ 'info_rse': info_rse,
2442
+ 'get_config': get_config,
2443
+ 'set_config_option': set_config_option,
2444
+ 'delete_config_option': delete_config_option,
2445
+ 'add_subscription': add_subscription,
2446
+ 'list_subscriptions': list_subscriptions,
2447
+ 'update_subscription': update_subscription,
2448
+ 'reevaluate_did_for_subscription': reevaluate_did_for_subscription,
2449
+ 'declare_bad_file_replicas': declare_bad_file_replicas,
2450
+ 'quarantine_replicas': quarantine_replicas,
2451
+ 'declare_temporary_unavailable_replicas': declare_temporary_unavailable_replicas,
2452
+ 'list_pfns': list_pfns,
2453
+ 'import': import_data,
2454
+ 'export': export_data,
2455
+ 'set_tombstone': set_tombstone,
2456
+ 'set_limit_rse': set_limit_rse,
2457
+ 'delete_limit_rse': delete_limit_rse,
2458
+ }
2459
+
2460
+ pager = get_pager()
2461
+ console = Console(theme=Theme(CLITheme.LOG_THEMES), soft_wrap=True)
2462
+ console.width = max(MIN_CONSOLE_WIDTH, console.width)
2463
+
2464
+ cli_config = get_cli_config()
2465
+ spinner = Status('Initializing spinner', spinner=CLITheme.SPINNER, spinner_style=CLITheme.SPINNER_STYLE, console=console)
2466
+
2467
+ if cli_config == 'rich':
2468
+ install(console=console, word_wrap=True, width=min(console.width, MAX_TRACEBACK_WIDTH)) # Make rich exception tracebacks the default.
2469
+ logger = setup_rich_logger(module_name=__name__, logger_name='user', verbose=args.verbose, console=console)
2470
+ else:
2471
+ logger = setup_logger(module_name=__name__, logger_name='user', verbose=args.verbose)
2472
+
2473
+ signal.signal(signal.SIGINT, lambda sig, frame: signal_handler(sig, frame, logger))
2474
+ setup_gfal2_logger()
2475
+ start_time = time.time()
2476
+ command = commands.get(args.which)
2477
+ client = get_client(args, logger)
2478
+ result = command(args, client, logger, console, spinner) # type: ignore
2479
+
2480
+ end_time = time.time()
2481
+ if cli_config == 'rich':
2482
+ spinner.stop()
2483
+ if console.is_terminal and not args.no_pager:
2484
+ command_output = console.end_capture()
2485
+ if command_output == '' and args.verbose:
2486
+ print("Completed in %-0.4f sec." % (end_time - start_time))
2487
+ else:
2488
+ if args.verbose:
2489
+ command_output += "Completed in %-0.4f sec." % (end_time - start_time)
2490
+ # Ignore SIGINT during pager execution.
2491
+ signal.signal(signal.SIGINT, signal.SIG_IGN)
2492
+ pager(command_output)
2493
+ else:
2494
+ if args.verbose:
2495
+ print("Completed in %-0.4f sec." % (end_time - start_time))
2496
+ sys.exit(result)
2497
+
2498
+
2499
+ if __name__ == '__main__':
2500
+ main()