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.
- rucio/alembicrevision.py +1 -1
- rucio/cli/__init__.py +14 -0
- rucio/cli/account.py +216 -0
- rucio/cli/bin_legacy/__init__.py +13 -0
- rucio_clients-35.7.0.data/scripts/rucio → rucio/cli/bin_legacy/rucio.py +769 -486
- rucio_clients-35.7.0.data/scripts/rucio-admin → rucio/cli/bin_legacy/rucio_admin.py +476 -423
- rucio/cli/command.py +272 -0
- rucio/cli/config.py +72 -0
- rucio/cli/did.py +191 -0
- rucio/cli/download.py +128 -0
- rucio/cli/lifetime_exception.py +33 -0
- rucio/cli/replica.py +162 -0
- rucio/cli/rse.py +293 -0
- rucio/cli/rule.py +158 -0
- rucio/cli/scope.py +40 -0
- rucio/cli/subscription.py +73 -0
- rucio/cli/upload.py +60 -0
- rucio/cli/utils.py +226 -0
- rucio/client/accountclient.py +0 -1
- rucio/client/baseclient.py +33 -24
- rucio/client/client.py +45 -1
- rucio/client/didclient.py +5 -3
- rucio/client/downloadclient.py +6 -8
- rucio/client/replicaclient.py +0 -2
- rucio/client/richclient.py +317 -0
- rucio/client/rseclient.py +4 -4
- rucio/client/uploadclient.py +26 -12
- rucio/common/bittorrent.py +234 -0
- rucio/common/cache.py +66 -29
- rucio/common/checksum.py +168 -0
- rucio/common/client.py +122 -0
- rucio/common/config.py +22 -35
- rucio/common/constants.py +61 -3
- rucio/common/didtype.py +72 -24
- rucio/common/exception.py +65 -8
- rucio/common/extra.py +5 -10
- rucio/common/logging.py +13 -13
- rucio/common/pcache.py +8 -7
- rucio/common/plugins.py +59 -27
- rucio/common/policy.py +12 -3
- rucio/common/schema/__init__.py +84 -34
- rucio/common/schema/generic.py +0 -17
- rucio/common/schema/generic_multi_vo.py +0 -17
- rucio/common/stomp_utils.py +383 -119
- rucio/common/test_rucio_server.py +12 -6
- rucio/common/types.py +132 -52
- rucio/common/utils.py +93 -643
- rucio/rse/__init__.py +3 -3
- rucio/rse/protocols/bittorrent.py +11 -1
- rucio/rse/protocols/cache.py +0 -11
- rucio/rse/protocols/dummy.py +0 -11
- rucio/rse/protocols/gfal.py +14 -9
- rucio/rse/protocols/globus.py +1 -1
- rucio/rse/protocols/http_cache.py +1 -1
- rucio/rse/protocols/posix.py +2 -2
- rucio/rse/protocols/protocol.py +84 -317
- rucio/rse/protocols/rclone.py +2 -1
- rucio/rse/protocols/rfio.py +10 -1
- rucio/rse/protocols/ssh.py +2 -1
- rucio/rse/protocols/storm.py +2 -13
- rucio/rse/protocols/webdav.py +74 -30
- rucio/rse/protocols/xrootd.py +2 -1
- rucio/rse/rsemanager.py +170 -53
- rucio/rse/translation.py +260 -0
- rucio/vcsversion.py +4 -4
- rucio/version.py +7 -0
- {rucio_clients-35.7.0.data → rucio_clients-37.0.0rc2.data}/data/etc/rucio.cfg.atlas.client.template +3 -2
- {rucio_clients-35.7.0.data → rucio_clients-37.0.0rc2.data}/data/etc/rucio.cfg.template +3 -19
- {rucio_clients-35.7.0.data → rucio_clients-37.0.0rc2.data}/data/requirements.client.txt +11 -7
- rucio_clients-37.0.0rc2.data/scripts/rucio +133 -0
- rucio_clients-37.0.0rc2.data/scripts/rucio-admin +97 -0
- {rucio_clients-35.7.0.dist-info → rucio_clients-37.0.0rc2.dist-info}/METADATA +18 -14
- rucio_clients-37.0.0rc2.dist-info/RECORD +104 -0
- {rucio_clients-35.7.0.dist-info → rucio_clients-37.0.0rc2.dist-info}/licenses/AUTHORS.rst +3 -0
- rucio/common/schema/atlas.py +0 -413
- rucio/common/schema/belleii.py +0 -408
- rucio/common/schema/domatpc.py +0 -401
- rucio/common/schema/escape.py +0 -426
- rucio/common/schema/icecube.py +0 -406
- rucio/rse/protocols/gsiftp.py +0 -92
- rucio_clients-35.7.0.dist-info/RECORD +0 -88
- {rucio_clients-35.7.0.data → rucio_clients-37.0.0rc2.data}/data/etc/rse-accounts.cfg.template +0 -0
- {rucio_clients-35.7.0.data → rucio_clients-37.0.0rc2.data}/data/rucio_client/merge_rucio_configs.py +0 -0
- {rucio_clients-35.7.0.dist-info → rucio_clients-37.0.0rc2.dist-info}/WHEEL +0 -0
- {rucio_clients-35.7.0.dist-info → rucio_clients-37.0.0rc2.dist-info}/licenses/LICENSE +0 -0
- {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)
|