rucio-clients 35.7.0__py3-none-any.whl → 37.0.0rc2__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 (86) hide show
  1. rucio/alembicrevision.py +1 -1
  2. rucio/cli/__init__.py +14 -0
  3. rucio/cli/account.py +216 -0
  4. rucio/cli/bin_legacy/__init__.py +13 -0
  5. rucio_clients-35.7.0.data/scripts/rucio → rucio/cli/bin_legacy/rucio.py +769 -486
  6. rucio_clients-35.7.0.data/scripts/rucio-admin → rucio/cli/bin_legacy/rucio_admin.py +476 -423
  7. rucio/cli/command.py +272 -0
  8. rucio/cli/config.py +72 -0
  9. rucio/cli/did.py +191 -0
  10. rucio/cli/download.py +128 -0
  11. rucio/cli/lifetime_exception.py +33 -0
  12. rucio/cli/replica.py +162 -0
  13. rucio/cli/rse.py +293 -0
  14. rucio/cli/rule.py +158 -0
  15. rucio/cli/scope.py +40 -0
  16. rucio/cli/subscription.py +73 -0
  17. rucio/cli/upload.py +60 -0
  18. rucio/cli/utils.py +226 -0
  19. rucio/client/accountclient.py +0 -1
  20. rucio/client/baseclient.py +33 -24
  21. rucio/client/client.py +45 -1
  22. rucio/client/didclient.py +5 -3
  23. rucio/client/downloadclient.py +6 -8
  24. rucio/client/replicaclient.py +0 -2
  25. rucio/client/richclient.py +317 -0
  26. rucio/client/rseclient.py +4 -4
  27. rucio/client/uploadclient.py +26 -12
  28. rucio/common/bittorrent.py +234 -0
  29. rucio/common/cache.py +66 -29
  30. rucio/common/checksum.py +168 -0
  31. rucio/common/client.py +122 -0
  32. rucio/common/config.py +22 -35
  33. rucio/common/constants.py +61 -3
  34. rucio/common/didtype.py +72 -24
  35. rucio/common/exception.py +65 -8
  36. rucio/common/extra.py +5 -10
  37. rucio/common/logging.py +13 -13
  38. rucio/common/pcache.py +8 -7
  39. rucio/common/plugins.py +59 -27
  40. rucio/common/policy.py +12 -3
  41. rucio/common/schema/__init__.py +84 -34
  42. rucio/common/schema/generic.py +0 -17
  43. rucio/common/schema/generic_multi_vo.py +0 -17
  44. rucio/common/stomp_utils.py +383 -119
  45. rucio/common/test_rucio_server.py +12 -6
  46. rucio/common/types.py +132 -52
  47. rucio/common/utils.py +93 -643
  48. rucio/rse/__init__.py +3 -3
  49. rucio/rse/protocols/bittorrent.py +11 -1
  50. rucio/rse/protocols/cache.py +0 -11
  51. rucio/rse/protocols/dummy.py +0 -11
  52. rucio/rse/protocols/gfal.py +14 -9
  53. rucio/rse/protocols/globus.py +1 -1
  54. rucio/rse/protocols/http_cache.py +1 -1
  55. rucio/rse/protocols/posix.py +2 -2
  56. rucio/rse/protocols/protocol.py +84 -317
  57. rucio/rse/protocols/rclone.py +2 -1
  58. rucio/rse/protocols/rfio.py +10 -1
  59. rucio/rse/protocols/ssh.py +2 -1
  60. rucio/rse/protocols/storm.py +2 -13
  61. rucio/rse/protocols/webdav.py +74 -30
  62. rucio/rse/protocols/xrootd.py +2 -1
  63. rucio/rse/rsemanager.py +170 -53
  64. rucio/rse/translation.py +260 -0
  65. rucio/vcsversion.py +4 -4
  66. rucio/version.py +7 -0
  67. {rucio_clients-35.7.0.data → rucio_clients-37.0.0rc2.data}/data/etc/rucio.cfg.atlas.client.template +3 -2
  68. {rucio_clients-35.7.0.data → rucio_clients-37.0.0rc2.data}/data/etc/rucio.cfg.template +3 -19
  69. {rucio_clients-35.7.0.data → rucio_clients-37.0.0rc2.data}/data/requirements.client.txt +11 -7
  70. rucio_clients-37.0.0rc2.data/scripts/rucio +133 -0
  71. rucio_clients-37.0.0rc2.data/scripts/rucio-admin +97 -0
  72. {rucio_clients-35.7.0.dist-info → rucio_clients-37.0.0rc2.dist-info}/METADATA +18 -14
  73. rucio_clients-37.0.0rc2.dist-info/RECORD +104 -0
  74. {rucio_clients-35.7.0.dist-info → rucio_clients-37.0.0rc2.dist-info}/licenses/AUTHORS.rst +3 -0
  75. rucio/common/schema/atlas.py +0 -413
  76. rucio/common/schema/belleii.py +0 -408
  77. rucio/common/schema/domatpc.py +0 -401
  78. rucio/common/schema/escape.py +0 -426
  79. rucio/common/schema/icecube.py +0 -406
  80. rucio/rse/protocols/gsiftp.py +0 -92
  81. rucio_clients-35.7.0.dist-info/RECORD +0 -88
  82. {rucio_clients-35.7.0.data → rucio_clients-37.0.0rc2.data}/data/etc/rse-accounts.cfg.template +0 -0
  83. {rucio_clients-35.7.0.data → rucio_clients-37.0.0rc2.data}/data/rucio_client/merge_rucio_configs.py +0 -0
  84. {rucio_clients-35.7.0.dist-info → rucio_clients-37.0.0rc2.dist-info}/WHEEL +0 -0
  85. {rucio_clients-35.7.0.dist-info → rucio_clients-37.0.0rc2.dist-info}/licenses/LICENSE +0 -0
  86. {rucio_clients-35.7.0.dist-info → rucio_clients-37.0.0rc2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,33 @@
1
+ # Copyright European Organization for Nuclear Research (CERN) since 2012
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ import click
15
+
16
+ from rucio.cli.bin_legacy.rucio import add_lifetime_exception
17
+ from rucio.cli.utils import Arguments
18
+
19
+
20
+ @click.group()
21
+ def lifetime_exception():
22
+ """Interact with the lifetime exception model"""
23
+
24
+
25
+ @lifetime_exception.command("add")
26
+ @click.option("-f", "--input-file", help="File where the list of datasets requested to be extended are located")
27
+ @click.option("--reason", help="The reason for the extension")
28
+ @click.option("-x", "--expiration", help="The expiration date format YYYY-MM-DD")
29
+ @click.pass_context
30
+ def add_(ctx, input_file, reason, expiration):
31
+ """Add an exception to the lifetime model""" # TODO description of what this does
32
+ args = Arguments({"inputfile": input_file, "reason": reason, "expiration": expiration})
33
+ add_lifetime_exception(args, ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
rucio/cli/replica.py ADDED
@@ -0,0 +1,162 @@
1
+ # Copyright European Organization for Nuclear Research (CERN) since 2012
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ import click
15
+
16
+ from rucio.cli.bin_legacy.rucio import list_dataset_replicas, list_file_replicas, list_suspicious_replicas
17
+ from rucio.cli.bin_legacy.rucio_admin import declare_bad_file_replicas, declare_temporary_unavailable_replicas, quarantine_replicas, set_tombstone
18
+ from rucio.cli.utils import Arguments
19
+
20
+
21
+ @click.group()
22
+ def replica():
23
+ """Manage replicas - DIDs with locations on RSEs"""
24
+
25
+
26
+ @replica.group("list")
27
+ def replica_list():
28
+ """List replicas (file or collection-types)"""
29
+
30
+
31
+ @replica_list.command("file")
32
+ @click.argument("dids", nargs=-1)
33
+ @click.option("--protocols", help="Protocol used to access a replicas (i.e. https, root, srm)")
34
+ @click.option(
35
+ "--all-states",
36
+ help="To select all replicas (including unavailable ones).\
37
+ Also gets information about the current state of a DID in each RSE",
38
+ is_flag=True,
39
+ default=False,
40
+ )
41
+ @click.option("--pfns", is_flag=True, help="Show only the PFNs", default=False)
42
+ @click.option("--domain", default="all", type=click.Choice(["wan", "lan", "all"]), help="Force the networking domain")
43
+ @click.option(
44
+ "--link",
45
+ help="Symlink PFNs with directory substitution.\
46
+ For example: rucio list-file-replicas --rse RSE_TEST --link /eos/:/eos/ scope:datasetname",
47
+ )
48
+ @click.option("--missing", is_flag=True, default=False, help="To list missing replicas at a RSE-Expression. Must be used with --rses option")
49
+ @click.option("--metalink", is_flag=True, default=False, help="Output available replicas as metalink")
50
+ @click.option("--no-resolve-archives", is_flag=True, default=False, help="Do not resolve archives which may contain the files", required=False)
51
+ @click.option("--sort", help="Replica sort algorithm. Available options: geoip (default), random")
52
+ @click.option("--rses", "--rse-exp", "rses", help="The RSE filter expression")
53
+ @click.option("--human", default=True, hidden=True)
54
+ @click.pass_context
55
+ def list_(ctx, dids, protocols, all_states, pfns, domain, link, missing, metalink, no_resolve_archives, sort, rses, human):
56
+ """List the replicas of a DID and its PFNs. By default all states, even unavailable, are shown"""
57
+ args = {"dids": dids, "protocols": protocols, "all_states": all_states, "pfns": pfns, "domain": domain, "link": link, "missing": missing, "metalink": metalink, "no_resolve_archives": no_resolve_archives, "sort": sort, "rses": rses, "human": human}
58
+ list_file_replicas(Arguments(args), ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
59
+
60
+
61
+ @replica_list.command("dataset")
62
+ @click.argument("dids", nargs=-1)
63
+ @click.option("--deep", default=False, is_flag=True, help="Make a deep check, checking the contents of datasets in datasets")
64
+ @click.option("--csv", help="Write output to comma separated values", is_flag=True, default=False)
65
+ @click.pass_context
66
+ def list_dataset(ctx, dids, deep, csv):
67
+ """List dataset replicas"""
68
+ args = Arguments({"dids": dids, "deep": deep, "csv": csv})
69
+ list_dataset_replicas(args, ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
70
+
71
+
72
+ @replica.command("remove")
73
+ @click.argument("dids", nargs=-1)
74
+ @click.option("--rse", "--rse-name", "rse", required=True)
75
+ @click.pass_context
76
+ def remove(ctx, dids, rse):
77
+ "Set a replica for removal by adding a tombstone which will mark the replica as ready for deletion by a reaper daemon"
78
+ # TODO: Fix set_tombstone to not expect a comma separated DID str
79
+ dids = ",".join(dids)
80
+ set_tombstone(Arguments({"dids": dids, "rse": rse}), ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
81
+
82
+
83
+ @replica.group()
84
+ @click.help_option("-h", "--help")
85
+ def state():
86
+ """Manage the state of replicas"""
87
+
88
+
89
+ @state.command("list")
90
+ @click.argument("state-type", type=click.Choice(["suspicious"]))
91
+ @click.option("--rses", "--rse-exp", help="RSE name or expression") # TODO remap rse_expression to rses (for consistency)
92
+ @click.option("--younger-than", help='List files that have been marked suspicious since the date "younger_than", e.g. 2021-11-29T00:00:00') # NOQA: E501
93
+ @click.option("--n-attempts", help="Minimum number of failed attempts to access a suspicious file")
94
+ @click.pass_context
95
+ def state_list(ctx, state_type, rses, younger_than, n_attempts):
96
+ """List replicas by state. WARNING: Only implemented for 'suspicious'"""
97
+
98
+ if state_type != "suspicious":
99
+ raise ValueError(f"Cannot list state by {state_type}, please choose from ('suspicious')")
100
+ list_suspicious_replicas(Arguments({"rse_expression": rses, "younger_than": younger_than, "nattempts": n_attempts}), ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
101
+
102
+
103
+ @state.group("update")
104
+ @click.help_option("-h", "--help")
105
+ def state_update():
106
+ "Change the state of replicas"
107
+
108
+
109
+ @state_update.command("bad")
110
+ @click.argument("replicas", nargs=-1)
111
+ @click.option("--reason", required=True, help="Reason")
112
+ @click.option("--as-file", is_flag=True, default=False, help="[REPLICAS] arg is a path to a file of replicas to update")
113
+ @click.option("--collection", is_flag=True, default=False, help="Items in the collection DID are also marked as bad")
114
+ @click.option("--lfn", is_flag=True, default=False, help="[REPLICAS] arg is a path to a file of LFNs. Requires --rse and --scope")
115
+ @click.option("--scope", help="Common scope for bad replicas specified with LFN list, ignored without --lfn")
116
+ @click.option("--rse", "--rse-name", help="Common RSE for bad replicas specified with LFN list, ignored without --lfn")
117
+ @click.pass_context
118
+ def update_bad(ctx, replicas, reason, as_file, collection, lfn, scope, rse):
119
+ """Mark a replica bad"""
120
+ args = {"reason": reason, "allow_collection": collection, "scope": scope, "rse": rse}
121
+ if as_file:
122
+ args["inputfile"] = replicas
123
+ elif lfn:
124
+ if (scope is None) or (rse is None):
125
+ raise ValueError("Scope and RSE are required when using LFNs")
126
+ args["lfns"] = replicas
127
+ else:
128
+ args["listbadfiles"] = replicas
129
+ declare_bad_file_replicas(Arguments(args), ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
130
+
131
+
132
+ @state_update.command("unavailable")
133
+ @click.argument("replicas", nargs=-1)
134
+ @click.option("--reason", required=True, help="Reason")
135
+ @click.option("--as-file", is_flag=True, default=False, help="[REPLICAS] arg is a path to a file of names to update")
136
+ @click.option("--duration", required=True, type=int, help="Timeout (in seconds) after which the replicas will become available again")
137
+ @click.pass_context
138
+ def update_unavailable(ctx, replicas, reason, as_file, duration):
139
+ """Declare a replica unavailable"""
140
+ args = {"reason": reason, "duration": duration}
141
+ if as_file:
142
+ args["inputfile"] = replicas
143
+ else:
144
+ args["listbadfiles"] = replicas
145
+ declare_temporary_unavailable_replicas(Arguments(args), ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
146
+
147
+
148
+ @state_update.command("quarantine")
149
+ @click.argument("replicas", nargs=-1)
150
+ @click.option("--as-file", is_flag=True, default=False, help="[REPLICAS] arg is a path to a file of names to update")
151
+ @click.option("--rse", "--rse-name") # TODO What does this do?
152
+ @click.pass_context
153
+ def update_quarantine(ctx, replicas, as_file, rse):
154
+ """Quarantine a replica"""
155
+ args = {"rse": rse}
156
+ if as_file:
157
+ args["paths_file"] = replicas
158
+ else:
159
+ args["paths_list"] = replicas
160
+
161
+ # TODO Add a reason option
162
+ quarantine_replicas(Arguments(args), ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
rucio/cli/rse.py ADDED
@@ -0,0 +1,293 @@
1
+ # Copyright European Organization for Nuclear Research (CERN) since 2012
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ import json
15
+
16
+ import click
17
+
18
+ from rucio.cli.bin_legacy.rucio import list_rses
19
+ from rucio.cli.bin_legacy.rucio_admin import (
20
+ add_distance_rses,
21
+ add_protocol_rse,
22
+ add_qos_policy,
23
+ add_rse,
24
+ del_protocol_rse,
25
+ delete_attribute_rse,
26
+ delete_distance_rses,
27
+ delete_limit_rse,
28
+ delete_qos_policy,
29
+ disable_rse,
30
+ get_attribute_rse,
31
+ get_distance_rses,
32
+ info_rse,
33
+ list_qos_policies,
34
+ set_attribute_rse,
35
+ set_limit_rse,
36
+ update_distance_rses,
37
+ update_rse,
38
+ )
39
+ from rucio.cli.utils import Arguments
40
+
41
+
42
+ @click.group()
43
+ def rse():
44
+ """Manage Rucio Storage Elements (RSEs)"""
45
+
46
+
47
+ @rse.command("list")
48
+ @click.option("--rses", "--rse-exp", help="RSE Expression to use as a filter", required=False)
49
+ @click.option("--csv", is_flag=True, default=False, help="Output list of RSEs as a csv")
50
+ @click.pass_context
51
+ def list_(ctx, rses, csv):
52
+ """List all registered Rucio Storage Elements (RSEs)"""
53
+ list_rses(Arguments({"rses": rses, "csv": csv}), ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
54
+
55
+
56
+ @rse.command("show")
57
+ @click.argument("rse-name")
58
+ @click.option("--csv", is_flag=True, default=False, help="Output list of RSE property key and values as a csv")
59
+ @click.pass_context
60
+ def show(ctx, rse_name, csv):
61
+ """Usage, protocols, settings, and attributes for a given RSE"""
62
+ info_rse(Arguments({"rse": rse_name, "csv": csv}), ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
63
+
64
+
65
+ @rse.command("add")
66
+ @click.argument("rse-name")
67
+ @click.option("--non-deterministic", is_flag=True, default=False, help="Create RSE in non-deterministic mode")
68
+ @click.pass_context
69
+ def add_(ctx, rse_name, non_deterministic):
70
+ """Add a new RSE"""
71
+ args = Arguments({"rse": rse_name, "non_deterministic": non_deterministic})
72
+ add_rse(args, ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
73
+
74
+
75
+ @rse.command("remove")
76
+ @click.argument("rse-name")
77
+ @click.pass_context
78
+ def remove(ctx, rse_name):
79
+ """Permanently disable an RSE. CAUTION: all information about the RSE might be lost!"""
80
+ disable_rse(Arguments({"rse": rse_name}), ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
81
+
82
+
83
+ @rse.command("update")
84
+ @click.argument("rse-name")
85
+ @click.option('--key', help='Setting key', required=True)
86
+ @click.option('--value', help='Setting value', required=True)
87
+ @click.pass_context
88
+ def update(ctx, rse_name, key, value):
89
+ """
90
+ Update an RSE's setting.
91
+
92
+ \b
93
+ Example:
94
+ $ rucio rse update my-rse --option availability_write True
95
+ """
96
+ args = Arguments({"rse": rse_name, "param": key, "value": value})
97
+ update_rse(args, ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
98
+
99
+
100
+ @rse.group()
101
+ @click.help_option("-h", "--help")
102
+ def distance():
103
+ """Manage the relative distance between RSEs for transfer prioritization calculations"""
104
+
105
+
106
+ @distance.command("show")
107
+ @click.argument("source-rse")
108
+ @click.argument("destination-rse")
109
+ @click.pass_context
110
+ def distance_show(ctx, source_rse, destination_rse):
111
+ """Display distance information from SOURCE-RSE to DESTINATION-RSE"""
112
+ get_distance_rses(Arguments({"source": source_rse, "destination": destination_rse}), ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
113
+
114
+
115
+ @distance.command("add")
116
+ @click.argument("source-rse")
117
+ @click.argument("destination-rse")
118
+ @click.option("--distance", default=1, type=int, help="Relative distance between RSEs")
119
+ @click.pass_context
120
+ def distance_add(ctx, source_rse, destination_rse, distance):
121
+ """Create a new link from SOURCE-RSE to DESTINATION-RSE with a distance"""
122
+ args = Arguments({"source": source_rse, "destination": destination_rse, "distance": distance})
123
+ add_distance_rses(args, ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
124
+
125
+
126
+ @distance.command("remove")
127
+ @click.argument("source-rse")
128
+ @click.argument("destination-rse")
129
+ @click.pass_context
130
+ def distance_remove(ctx, source_rse, destination_rse):
131
+ """Un-link SOURCE-RSE from DESTINATION-RSE by removing the distance between them"""
132
+ args = Arguments({"source": source_rse, "destination": destination_rse})
133
+ delete_distance_rses(args, ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
134
+
135
+
136
+ @distance.command("update")
137
+ @click.argument("source-rse")
138
+ @click.argument("destination-rse")
139
+ @click.option("--distance", type=int, help="Relative distance between RSEs", required=True)
140
+ @click.pass_context
141
+ def distance_update(ctx, source_rse, destination_rse, distance):
142
+ """Update the existing distance or ranking from SOURCE-RSE to DESTINATION-RSE"""
143
+ args = Arguments({"source": source_rse, "destination": destination_rse, "distance": distance})
144
+ update_distance_rses(args, ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
145
+
146
+
147
+ @rse.group()
148
+ def attribute():
149
+ """Interact with RSE Attributes"""
150
+
151
+
152
+ @attribute.command("list")
153
+ @click.argument("rse-name")
154
+ @click.pass_context
155
+ def attr_list_(ctx, rse_name):
156
+ """List all attributes of a given RSE"""
157
+ get_attribute_rse(Arguments({"rse": rse_name}), ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
158
+
159
+
160
+ @attribute.command("add")
161
+ @click.argument("rse-name")
162
+ @click.option('--key', help='Attribute key', required=True)
163
+ @click.option('--value', help='Attribute value', required=True)
164
+ @click.pass_context
165
+ def attribute_add_(ctx, rse_name, key, value):
166
+ """Add a new attribute for an RSE
167
+
168
+ \b
169
+ Example:
170
+ $ rucio rse attribute add my-rse --key My-Attribute --value True
171
+ """
172
+ args = Arguments({"rse": rse_name, "key": key, "value": value})
173
+ set_attribute_rse(args, ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
174
+
175
+
176
+ # TODO Update attribute - only overwrites existing attributes
177
+
178
+ @attribute.command("remove")
179
+ @click.argument("rse-name")
180
+ @click.option("--attribute", help="Attribute to remove", required=True)
181
+ @click.pass_context
182
+ def attribute_remove(ctx, rse_name, attribute):
183
+ """Remove an existing attribute from an RSE"""
184
+ args = Arguments({"rse": rse_name, "key": attribute, "value": None})
185
+ delete_attribute_rse(args, ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
186
+
187
+
188
+ @rse.group()
189
+ def limit():
190
+ """Manage storage size limits"""
191
+
192
+
193
+ @limit.command("add")
194
+ @click.argument("rse-name")
195
+ @click.option("--limit", type=(str, int), required=True, help="Name of limit and value in bytes")
196
+ @click.pass_context
197
+ def limit_add(ctx, rse_name, limit):
198
+ """Add a usage limit to an RSE
199
+
200
+ \b
201
+ Example, add a limit of 1KB to XRD1 named "MinFreeSpace":
202
+ $ rucio rse limit add XRD1 --limit MinFreeSpace 10000
203
+ """
204
+ args = Arguments({"rse": rse_name, "name": limit[0], "value": limit[1]})
205
+ set_limit_rse(args, ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
206
+
207
+
208
+ @limit.command("remove")
209
+ @click.argument("rse-name")
210
+ @click.option("--limit", required=True, help="Name of limit to remove")
211
+ @click.pass_context
212
+ def limit_remove(ctx, rse_name, limit):
213
+ """Remove an existing RSE limit"""
214
+ args = Arguments({"rse": rse_name, "name": limit})
215
+ delete_limit_rse(args, ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
216
+
217
+
218
+ @rse.group()
219
+ @click.help_option("-h", "--help")
220
+ def protocol():
221
+ """Manage RSE transfer protocols"""
222
+
223
+
224
+ # TODO Better loader for json types
225
+ @protocol.command("add")
226
+ @click.argument("rse-name")
227
+ @click.option("--host", "--host-name", help="Endpoint hostname", required=True)
228
+ @click.option("--scheme", help="Endpoint URL scheme", required=True)
229
+ @click.option("--prefix", help="Endpoint URL path prefix", required=True)
230
+ @click.option("--space-token", help="Space token name (SRM-only)")
231
+ @click.option("--web-service-path", help="Web service URL (SRM-only)")
232
+ @click.option("--port", type=int, help="URL port")
233
+ @click.option("--impl", default="rucio.rse.protocols.gfal.Default", help="Transfer protocol implementation to use")
234
+ @click.option("--domain-json", type=json.loads, help="JSON describing the WAN / LAN setup")
235
+ @click.option("--extended-attributes-json", type=json.loads, help="JSON describing any extended attributes")
236
+ @click.pass_context
237
+ def protocol_add(ctx, rse_name, host, scheme, prefix, space_token, web_service_path, port, impl, domain_json, extended_attributes_json):
238
+ """
239
+ Add a new protocol for an RSE used for transferring files
240
+
241
+ \b
242
+ Example, adding a default protocol hosted at jdoes.test.org to the RSE JDOE_DATADISK
243
+ $ rucio rse protocol add JDOE_DATADISK --host-name jdoes.test.org --scheme gsiftp --prefix '/atlasdatadisk/rucio/' --port 8443'
244
+
245
+ """
246
+ args = Arguments(
247
+ {"rse": rse_name, "hostname": host, "ext_attr_json": extended_attributes_json, "scheme": scheme, "prefix": prefix, "space_token": space_token, "web_service_path": web_service_path, "port": port, "impl": impl, "domain_json": domain_json}
248
+ )
249
+ add_protocol_rse(args, ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
250
+
251
+
252
+ @protocol.command("remove")
253
+ @click.argument("rse-name")
254
+ @click.option("--scheme", help="Endpoint URL scheme", required=True)
255
+ @click.option("--host-name", help="Endpoint hostname")
256
+ @click.option("--port", type=int, help="URL port")
257
+ @click.pass_context
258
+ def protocol_remove(ctx, rse_name, host_name, scheme, port):
259
+ """Remove an existing protocol from an RSE"""
260
+ args = Arguments({"rse": rse_name, "scheme": scheme, "hostname": host_name, "port": port})
261
+ del_protocol_rse(args, ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
262
+
263
+
264
+ @rse.group()
265
+ @click.help_option("-h", "--help")
266
+ def qos():
267
+ """Interact with the QoS model"""
268
+
269
+
270
+ @qos.command("add")
271
+ @click.argument("rse-name", nargs=1)
272
+ @click.option("--policy", required=True)
273
+ @click.pass_context
274
+ def qos_add(ctx, rse_name, policy):
275
+ "Add a new QoS policy"
276
+ add_qos_policy(Arguments({"rse": rse_name, "qos_policy": policy}), ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
277
+
278
+
279
+ @qos.command("remove")
280
+ @click.argument("rse-name", nargs=1)
281
+ @click.option("--policy", required=True)
282
+ @click.pass_context
283
+ def qos_remove(ctx, rse_name, policy):
284
+ "Remove an existing QoS policy"
285
+ delete_qos_policy(Arguments({"rse": rse_name, "qos_policy": policy}), ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
286
+
287
+
288
+ @qos.command("list")
289
+ @click.argument("rse-name", nargs=1)
290
+ @click.pass_context
291
+ def qos_list(ctx, rse_name):
292
+ "List the RSE's QoS policies"
293
+ list_qos_policies(Arguments({"rse": rse_name}), ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
rucio/cli/rule.py ADDED
@@ -0,0 +1,158 @@
1
+ # Copyright European Organization for Nuclear Research (CERN) since 2012
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ import click
15
+
16
+ from rucio.cli.bin_legacy.rucio import add_rule, delete_rule, info_rule, list_rules, list_rules_history, move_rule, update_rule
17
+ from rucio.cli.utils import Arguments
18
+
19
+
20
+ @click.group()
21
+ def rule():
22
+ """View and define rules for creating replicas of DIDs"""
23
+
24
+
25
+ @rule.command("add")
26
+ @click.argument("dids", nargs=-1)
27
+ @click.option("--copies", type=int, help="Number of copies", required=True)
28
+ @click.option("--rses", "--rse-exp", help="Rule RSE expression", required=True)
29
+ @click.option("--weight", help="RSE Weight")
30
+ @click.option("--lifetime", type=str, help="Rule lifetime (in seconds). Use 'None' for no set lifetime")
31
+ @click.option("--grouping", type=click.Choice(["DATASET", "ALL", "NONE"]), help="Rule grouping")
32
+ @click.option("--locked", default=False, type=bool, is_flag=False, help="Rule locking")
33
+ @click.option("--source-rses", help="RSE Expression for RSEs to be considered for source replicas")
34
+ @click.option("--notify", type=click.Choice(["Y", "N", "C"]), help="Notification strategy : Y (Yes), N (No), C (Close)")
35
+ @click.option("--activity", help="Activity to be used (e.g. User, Data Consolidation)")
36
+ @click.option("--comment", help="Comment about the replication rule")
37
+ @click.option("--ask-approval", is_flag=True, default=False, help="Ask for rule approval")
38
+ @click.option("--asynchronous", is_flag=True, default=False, help="Create rule asynchronously")
39
+ @click.option("--delay-injection", type=int, help="Delay (in seconds) to wait before starting applying the rule. This option implies --asynchronous.")
40
+ @click.option("--account", help="The account owning the rule")
41
+ @click.option("--skip-duplicates", is_flag=True, default=False, help="Skip duplicate rules")
42
+ @click.pass_context
43
+ def add_(ctx, dids, copies, rses, weight, asynchronous, lifetime, grouping, locked, source_rses, notify, activity, comment, ask_approval, delay_injection, account, skip_duplicates):
44
+ """Add replication rule to define how replicas of a list of DIDs are created on RSEs."""
45
+ args = Arguments(
46
+ {
47
+ "dids": dids,
48
+ "copies": copies,
49
+ "rse_expression": rses,
50
+ "weight": weight,
51
+ "lifetime": lifetime,
52
+ "grouping": grouping,
53
+ "locked": locked,
54
+ "notify": notify,
55
+ "activity": activity,
56
+ "comment": comment,
57
+ "ask_approval": ask_approval,
58
+ "delay_injection": delay_injection,
59
+ "rule_account": account,
60
+ "source_replica_expression": source_rses,
61
+ "ignore_duplicate": skip_duplicates,
62
+ "asynchronous": asynchronous,
63
+ }
64
+ )
65
+ add_rule(args, ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
66
+
67
+
68
+ @rule.command("remove")
69
+ @click.argument("rule-id-dids")
70
+ @click.option("--purge-replicas", is_flag=True, default=False, help="Purge rule replicas")
71
+ @click.option("--all", "_all", is_flag=True, default=False, help="Delete all the rules, even the ones that are not owned by the account")
72
+ @click.option("--rses", "--rse-exp", help="The RSE expression. Must be specified if a DID is provided.") # TODO mutual inclusive group
73
+ @click.option("--account", help="The account of the rule that must be deleted")
74
+ @click.pass_context
75
+ def remove(ctx, rule_id_dids, _all, rses, account, purge_replicas):
76
+ """Remove an existing rule. Supply [rule-id] if know, or use [DID] and --rses to remove all rules for DIDs on RSEs matching the expression"""
77
+ args = Arguments({"purge_replicas": purge_replicas, "delete_all": _all, "rule_account": account, "rule_id": rule_id_dids, "rses": rses})
78
+ delete_rule(args, ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
79
+
80
+
81
+ @rule.command("show")
82
+ @click.argument("rule-id")
83
+ @click.option("--examine", is_flag=True, default=False, help="Detailed analysis of transfer errors")
84
+ @click.pass_context
85
+ def show(ctx, rule_id, examine):
86
+ """Retrieve information about a rule"""
87
+ info_rule(Arguments({"rule_id": rule_id, "examine": examine}), ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
88
+
89
+
90
+ @rule.command("history")
91
+ @click.argument("did", nargs=1)
92
+ @click.pass_context
93
+ def history(ctx, did):
94
+ """Display the history of rules acting on a DID"""
95
+ list_rules_history(Arguments({"did": did}), ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
96
+
97
+
98
+ @rule.command("move")
99
+ @click.argument("rule_id")
100
+ @click.option("--rses", "--rse-exp", help="RSE expression of new rule", required=True)
101
+ @click.option("--activity", help="Update activity for moved rule", hidden=True) # Should only do this using `update`
102
+ @click.option("--source-rses", help="Update how replicas are sourced for the rule")
103
+ @click.pass_context
104
+ def move(ctx, rule_id, rses, activity, source_rses):
105
+ """Create a child rule on a different RSE. The parent rule is deleted once the new rule reaches `OK` status"""
106
+ args = Arguments({"rule_id": rule_id, "rse_expression": rses, "source_replica_expression": source_rses, "activity": activity})
107
+ move_rule(args, ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
108
+
109
+
110
+ @rule.command("update")
111
+ @click.argument("rule-id", nargs=1)
112
+ @click.option("--lifetime", type=str, help="Rule lifetime (in seconds). Use 'None' for no set lifetime")
113
+ @click.option("--locked", default=False, type=bool, is_flag=False, help="Rule locking")
114
+ @click.option("--source-rses", help="RSE Expression for RSEs to be considered for source replicas")
115
+ @click.option("--activity", help="Activity to be used (e.g. User, Data Consolidation)")
116
+ @click.option("--comment", help="Comment about the replication rule")
117
+ @click.option("--account", help="The account owning the rule")
118
+ @click.option("--stuck", is_flag=True, default=False, help="Set state to STUCK.")
119
+ @click.option("--activity", help="Activity of the rule.")
120
+ @click.option("--cancel-requests", is_flag=True, default=False, help="Cancel requests when setting rules to stuck.")
121
+ @click.option("--priority", help="Priority of the requests of the rule.")
122
+ @click.option("--child-rule-id", help='Child rule id of the rule. Use "None" to remove an existing parent/child relationship.')
123
+ @click.option("--boost-rule", is_flag=True, default=False, help="Quickens the transition of a rule from STUCK to REPLICATING.")
124
+ @click.pass_context
125
+ def update(ctx, rule_id, lifetime, locked, source_rses, activity, comment, account, stuck, cancel_requests, priority, child_rule_id, boost_rule):
126
+ """Update an existing rule"""
127
+ args = Arguments(
128
+ {
129
+ "rule_id": rule_id,
130
+ "lifetime": lifetime,
131
+ "locked": str(locked), # update-rule wants to be able to uppercase arg
132
+ "rule_activity": activity,
133
+ "comment": comment,
134
+ "rule_account": account,
135
+ "source_replica_expression": source_rses,
136
+ "state_stuck": stuck,
137
+ "cancel_requests": cancel_requests,
138
+ "priority": priority,
139
+ "child_rule_id": child_rule_id,
140
+ "boost_rule": boost_rule,
141
+ }
142
+ )
143
+ update_rule(args, ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)
144
+
145
+
146
+ @rule.command("list")
147
+ @click.option("--did")
148
+ @click.option("--id", "rule_id", help="List by rule id", hidden=True) # TODO: Remove. This doesn't work and does the same thing as show
149
+ @click.option("--traverse", is_flag=True, default=False, help="Traverse the did tree and search for rules affecting this did")
150
+ @click.option("--csv", is_flag=True, default=False, help="Comma Separated Value output")
151
+ @click.option("--file", help="Filter by file")
152
+ @click.option("--account", help="Filter by account")
153
+ @click.option("--subscription", help="Filter by subscription name")
154
+ @click.pass_context
155
+ def list_(ctx, did, rule_id, traverse, csv, file, account, subscription):
156
+ """List all rules impacting a given DID"""
157
+ args = Arguments({"did": did, "rule_id": rule_id, "traverse": traverse, "csv": csv, "file": file, "subscription": (account if account is not None else ctx.obj.client.account, subscription)})
158
+ list_rules(args, ctx.obj.client, ctx.obj.logger, ctx.obj.console, ctx.obj.spinner)