moat-kv 0.71.0__py3-none-any.whl → 0.71.7__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.
- moat/kv/__init__.py +6 -7
- moat/kv/_cfg.yaml +3 -2
- moat/kv/actor/__init__.py +2 -1
- moat/kv/actor/deletor.py +4 -1
- moat/kv/auth/__init__.py +12 -13
- moat/kv/auth/_test.py +4 -1
- moat/kv/auth/password.py +11 -7
- moat/kv/backend/mqtt.py +4 -5
- moat/kv/client.py +20 -39
- moat/kv/code.py +3 -3
- moat/kv/command/data.py +4 -3
- moat/kv/command/dump/__init__.py +36 -34
- moat/kv/command/internal.py +2 -3
- moat/kv/command/job.py +1 -2
- moat/kv/command/type.py +3 -6
- moat/kv/data.py +9 -8
- moat/kv/errors.py +16 -8
- moat/kv/mock/__init__.py +2 -12
- moat/kv/model.py +29 -33
- moat/kv/obj/__init__.py +3 -3
- moat/kv/obj/command.py +3 -3
- moat/kv/runner.py +4 -5
- moat/kv/server.py +106 -126
- moat/kv/types.py +10 -12
- {moat_kv-0.71.0.dist-info → moat_kv-0.71.7.dist-info}/METADATA +6 -2
- moat_kv-0.71.7.dist-info/RECORD +47 -0
- {moat_kv-0.71.0.dist-info → moat_kv-0.71.7.dist-info}/WHEEL +1 -1
- moat_kv-0.71.7.dist-info/licenses/LICENSE +3 -0
- moat_kv-0.71.7.dist-info/licenses/LICENSE.APACHE2 +202 -0
- moat_kv-0.71.7.dist-info/licenses/LICENSE.MIT +20 -0
- moat_kv-0.71.7.dist-info/top_level.txt +1 -0
- build/lib/docs/source/conf.py +0 -201
- build/lib/examples/pathify.py +0 -45
- build/lib/moat/kv/__init__.py +0 -19
- build/lib/moat/kv/_cfg.yaml +0 -93
- build/lib/moat/kv/_main.py +0 -91
- build/lib/moat/kv/actor/__init__.py +0 -98
- build/lib/moat/kv/actor/deletor.py +0 -139
- build/lib/moat/kv/auth/__init__.py +0 -444
- build/lib/moat/kv/auth/_test.py +0 -166
- build/lib/moat/kv/auth/password.py +0 -234
- build/lib/moat/kv/auth/root.py +0 -58
- build/lib/moat/kv/backend/__init__.py +0 -67
- build/lib/moat/kv/backend/mqtt.py +0 -71
- build/lib/moat/kv/client.py +0 -1025
- build/lib/moat/kv/code.py +0 -236
- build/lib/moat/kv/codec.py +0 -11
- build/lib/moat/kv/command/__init__.py +0 -1
- build/lib/moat/kv/command/acl.py +0 -180
- build/lib/moat/kv/command/auth.py +0 -261
- build/lib/moat/kv/command/code.py +0 -293
- build/lib/moat/kv/command/codec.py +0 -186
- build/lib/moat/kv/command/data.py +0 -265
- build/lib/moat/kv/command/dump/__init__.py +0 -143
- build/lib/moat/kv/command/error.py +0 -149
- build/lib/moat/kv/command/internal.py +0 -248
- build/lib/moat/kv/command/job.py +0 -433
- build/lib/moat/kv/command/log.py +0 -53
- build/lib/moat/kv/command/server.py +0 -114
- build/lib/moat/kv/command/type.py +0 -201
- build/lib/moat/kv/config.py +0 -46
- build/lib/moat/kv/data.py +0 -216
- build/lib/moat/kv/errors.py +0 -561
- build/lib/moat/kv/exceptions.py +0 -126
- build/lib/moat/kv/mock/__init__.py +0 -101
- build/lib/moat/kv/mock/mqtt.py +0 -159
- build/lib/moat/kv/mock/tracer.py +0 -63
- build/lib/moat/kv/model.py +0 -1069
- build/lib/moat/kv/obj/__init__.py +0 -646
- build/lib/moat/kv/obj/command.py +0 -241
- build/lib/moat/kv/runner.py +0 -1347
- build/lib/moat/kv/server.py +0 -2809
- build/lib/moat/kv/types.py +0 -513
- ci/rtd-requirements.txt +0 -4
- ci/test-requirements.txt +0 -7
- ci/travis.sh +0 -96
- debian/.gitignore +0 -7
- debian/changelog +0 -1435
- debian/control +0 -43
- debian/moat-kv/usr/lib/python3/dist-packages/docs/source/conf.py +0 -201
- debian/moat-kv/usr/lib/python3/dist-packages/examples/pathify.py +0 -45
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/__init__.py +0 -19
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/_cfg.yaml +0 -93
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/_main.py +0 -91
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/actor/__init__.py +0 -98
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/actor/deletor.py +0 -139
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/__init__.py +0 -444
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/_test.py +0 -166
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/password.py +0 -234
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/auth/root.py +0 -58
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/backend/__init__.py +0 -67
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/backend/mqtt.py +0 -71
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/client.py +0 -1025
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/code.py +0 -236
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/codec.py +0 -11
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/__init__.py +0 -1
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/acl.py +0 -180
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/auth.py +0 -261
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/code.py +0 -293
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/codec.py +0 -186
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/data.py +0 -265
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/dump/__init__.py +0 -143
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/error.py +0 -149
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/internal.py +0 -248
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/job.py +0 -433
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/log.py +0 -53
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/server.py +0 -114
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/command/type.py +0 -201
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/config.py +0 -46
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/data.py +0 -216
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/errors.py +0 -561
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/exceptions.py +0 -126
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/__init__.py +0 -101
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/mqtt.py +0 -159
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/mock/tracer.py +0 -63
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/model.py +0 -1069
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/obj/__init__.py +0 -646
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/obj/command.py +0 -241
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/runner.py +0 -1347
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/server.py +0 -2809
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/types.py +0 -513
- debian/moat-kv.postinst +0 -3
- debian/rules +0 -20
- debian/source/format +0 -1
- debian/watch +0 -4
- docs/Makefile +0 -20
- docs/make.bat +0 -36
- docs/source/TODO.rst +0 -61
- docs/source/_static/.gitkeep +0 -0
- docs/source/acls.rst +0 -80
- docs/source/auth.rst +0 -84
- docs/source/client_protocol.rst +0 -456
- docs/source/code.rst +0 -341
- docs/source/command_line.rst +0 -1187
- docs/source/common_protocol.rst +0 -47
- docs/source/conf.py +0 -201
- docs/source/debugging.rst +0 -70
- docs/source/extend.rst +0 -37
- docs/source/history.rst +0 -36
- docs/source/index.rst +0 -75
- docs/source/model.rst +0 -54
- docs/source/overview.rst +0 -83
- docs/source/related.rst +0 -89
- docs/source/server_protocol.rst +0 -450
- docs/source/startup.rst +0 -31
- docs/source/translator.rst +0 -244
- docs/source/tutorial.rst +0 -711
- docs/source/v3.rst +0 -168
- examples/code/transform.scale.yml +0 -21
- examples/code/transform.switch.yml +0 -82
- examples/code/transform.timeslot.yml +0 -63
- examples/pathify.py +0 -45
- moat/kv/codec.py +0 -11
- moat_kv-0.71.0.dist-info/RECORD +0 -188
- moat_kv-0.71.0.dist-info/top_level.txt +0 -9
- scripts/current +0 -15
- scripts/env +0 -8
- scripts/init +0 -39
- scripts/recover +0 -17
- scripts/rotate +0 -33
- scripts/run +0 -29
- scripts/run-all +0 -10
- scripts/run-any +0 -10
- scripts/run-single +0 -15
- scripts/success +0 -4
- systemd/moat-kv-recover.service +0 -21
- systemd/moat-kv-rotate.service +0 -20
- systemd/moat-kv-rotate.timer +0 -10
- systemd/moat-kv-run-all.service +0 -26
- systemd/moat-kv-run-all@.service +0 -25
- systemd/moat-kv-run-any.service +0 -26
- systemd/moat-kv-run-any@.service +0 -25
- systemd/moat-kv-run-single.service +0 -26
- systemd/moat-kv-run-single@.service +0 -25
- systemd/moat-kv.service +0 -27
- systemd/postinst +0 -7
- systemd/sysusers +0 -3
- {moat_kv-0.71.0.dist-info → moat_kv-0.71.7.dist-info}/licenses/LICENSE.txt +0 -0
@@ -1,248 +0,0 @@
|
|
1
|
-
# command line interface
|
2
|
-
from __future__ import annotations
|
3
|
-
|
4
|
-
from collections.abc import Mapping
|
5
|
-
|
6
|
-
import asyncclick as click
|
7
|
-
from moat.util import P, PathLongener, yprint
|
8
|
-
from range_set import RangeSet
|
9
|
-
|
10
|
-
|
11
|
-
@click.group(short_help="Control internal state.") # pylint: disable=undefined-variable
|
12
|
-
async def cli():
|
13
|
-
"""
|
14
|
-
This subcommand queries and controls the server's internal state.
|
15
|
-
"""
|
16
|
-
pass
|
17
|
-
|
18
|
-
|
19
|
-
@cli.command()
|
20
|
-
@click.option("-n", "--nodes", is_flag=True, help="Get node status.")
|
21
|
-
@click.option("-d", "--deleted", is_flag=True, help="Get deletion status.")
|
22
|
-
@click.option("-m", "--missing", is_flag=True, help="Get missing-node status.")
|
23
|
-
@click.option(
|
24
|
-
"-r",
|
25
|
-
"--remote-missing",
|
26
|
-
"remote_missing",
|
27
|
-
is_flag=True,
|
28
|
-
help="Get remote-missing-node status.",
|
29
|
-
)
|
30
|
-
@click.option("-p", "--present", is_flag=True, help="Get known-data status.")
|
31
|
-
@click.option("-s", "--superseded", is_flag=True, help="Get superseded-data status.")
|
32
|
-
@click.option("-D", "--debug", is_flag=True, help="Get internal verbosity.")
|
33
|
-
@click.option("--debugger", is_flag=True, help="Start a remote debugger. DO NOT USE.")
|
34
|
-
@click.option("-k", "--known", hidden=True, is_flag=True, help="Get superseded-data status.")
|
35
|
-
@click.option("-a", "--all", is_flag=True, help="All available data.")
|
36
|
-
@click.pass_obj
|
37
|
-
async def state(obj, **flags):
|
38
|
-
"""
|
39
|
-
Dump the server's state.
|
40
|
-
"""
|
41
|
-
if flags.pop("known", None):
|
42
|
-
flags["superseded"] = True
|
43
|
-
if flags.pop("all", None):
|
44
|
-
flags["superseded"] = True
|
45
|
-
flags["present"] = True
|
46
|
-
flags["nodes"] = True
|
47
|
-
flags["deleted"] = True
|
48
|
-
flags["missing"] = True
|
49
|
-
flags["remote_missing"] = True
|
50
|
-
res = await obj.client._request("get_state", iter=False, **flags)
|
51
|
-
k = res.pop("known", None)
|
52
|
-
if k is not None:
|
53
|
-
res["superseded"] = k
|
54
|
-
yprint(res, stream=obj.stdout)
|
55
|
-
|
56
|
-
|
57
|
-
@cli.command()
|
58
|
-
@click.option("-d", "--deleted", is_flag=True, help="Mark as deleted. Default: superseded")
|
59
|
-
@click.option(
|
60
|
-
"-n",
|
61
|
-
"--node",
|
62
|
-
"source",
|
63
|
-
default="?",
|
64
|
-
help="The node this message is faked as being from.",
|
65
|
-
)
|
66
|
-
@click.option("-b", "--broadcast", is_flag=True, help="Send to all servers")
|
67
|
-
@click.argument("node", nargs=1)
|
68
|
-
@click.argument("items", type=int, nargs=-1)
|
69
|
-
@click.pass_obj
|
70
|
-
async def mark(obj, deleted, source, node, items, broadcast):
|
71
|
-
"""
|
72
|
-
Fix internal state. Use no items to fetch the current list from the
|
73
|
-
server's ``missing`` state. Use an empty node name to add the whole
|
74
|
-
list, not just a single node's.
|
75
|
-
|
76
|
-
This is a dangerous command.
|
77
|
-
"""
|
78
|
-
|
79
|
-
k = "deleted" if deleted else "superseded"
|
80
|
-
if not items:
|
81
|
-
r = await obj.client._request("get_state", iter=False, missing=True)
|
82
|
-
r = r["missing"]
|
83
|
-
if node != "":
|
84
|
-
r = {node: r[node]}
|
85
|
-
elif node == "":
|
86
|
-
raise click.UsageError("You can't do that with an empty node")
|
87
|
-
else:
|
88
|
-
r = RangeSet()
|
89
|
-
for i in items:
|
90
|
-
r.add(i)
|
91
|
-
r = {node: r.__getstate__()}
|
92
|
-
|
93
|
-
msg = {k: r, "node": source}
|
94
|
-
|
95
|
-
await obj.client._request("fake_info", iter=False, **msg)
|
96
|
-
if broadcast:
|
97
|
-
await obj.client._request("fake_info_send", iter=False, **msg)
|
98
|
-
|
99
|
-
res = await obj.client._request("get_state", iter=False, **{k: True})
|
100
|
-
yprint(res, stream=obj.stdout)
|
101
|
-
|
102
|
-
|
103
|
-
@cli.command(short_help="Manage the Deleter list")
|
104
|
-
@click.option("-d", "--delete", is_flag=True, help="Remove these nodes")
|
105
|
-
@click.argument("nodes", nargs=-1)
|
106
|
-
@click.pass_obj
|
107
|
-
async def deleter(obj, delete, nodes):
|
108
|
-
"""
|
109
|
-
Manage the Deleter list
|
110
|
-
|
111
|
-
This is the set of nodes that must be online for removal of deleted
|
112
|
-
entries from MoaT-KV's data.
|
113
|
-
|
114
|
-
There should be one such node in every possible network partition.
|
115
|
-
Also, all nodes with permanent storage should be on the list.
|
116
|
-
|
117
|
-
Usage:
|
118
|
-
- … deleter -- list state
|
119
|
-
- … deleter NODE… -- add this node
|
120
|
-
- … deleter -d NODE… -- remove this node
|
121
|
-
"""
|
122
|
-
|
123
|
-
res = await obj.client._request(
|
124
|
-
action="get_internal",
|
125
|
-
path=("actor", "del"),
|
126
|
-
iter=False,
|
127
|
-
nchain=3 if delete or nodes else 2,
|
128
|
-
)
|
129
|
-
val = res.get("value", {})
|
130
|
-
if isinstance(val, Mapping):
|
131
|
-
val = val.get("nodes", [])
|
132
|
-
# else: compatibility, TODO remove
|
133
|
-
val = set(val)
|
134
|
-
if delete:
|
135
|
-
if nodes:
|
136
|
-
val -= set(nodes)
|
137
|
-
else:
|
138
|
-
val = set()
|
139
|
-
elif nodes:
|
140
|
-
val |= set(nodes)
|
141
|
-
else:
|
142
|
-
yprint(res, stream=obj.stdout)
|
143
|
-
return
|
144
|
-
|
145
|
-
val = {"nodes": list(val)}
|
146
|
-
res = await obj.client._request(
|
147
|
-
action="set_internal",
|
148
|
-
path=("actor", "del"),
|
149
|
-
iter=False,
|
150
|
-
chain=res.chain,
|
151
|
-
value=val,
|
152
|
-
)
|
153
|
-
res.value = val
|
154
|
-
yprint(res, stream=obj.stdout)
|
155
|
-
|
156
|
-
|
157
|
-
@cli.command()
|
158
|
-
@click.argument("path", nargs=1)
|
159
|
-
@click.pass_obj
|
160
|
-
async def dump(obj, path):
|
161
|
-
"""
|
162
|
-
Dump internal state.
|
163
|
-
|
164
|
-
This displays MoaT-KV's internal state.
|
165
|
-
"""
|
166
|
-
|
167
|
-
path = P(path)
|
168
|
-
y = {}
|
169
|
-
pl = PathLongener()
|
170
|
-
async for r in await obj.client._request("get_tree_internal", path=path, iter=True, nchain=0):
|
171
|
-
pl(r)
|
172
|
-
path = r["path"]
|
173
|
-
yy = y
|
174
|
-
for p in path:
|
175
|
-
yy = yy.setdefault(p, {})
|
176
|
-
try:
|
177
|
-
yy["_"] = r["value"]
|
178
|
-
except KeyError:
|
179
|
-
pass
|
180
|
-
yprint(y, stream=obj.stdout)
|
181
|
-
|
182
|
-
|
183
|
-
@cli.command()
|
184
|
-
@click.argument("node", nargs=1)
|
185
|
-
@click.argument("tick", type=int, nargs=1)
|
186
|
-
@click.pass_obj
|
187
|
-
async def get(obj, node, tick):
|
188
|
-
"""
|
189
|
-
Fetch data by node+tick.
|
190
|
-
|
191
|
-
This looks up internal data.
|
192
|
-
"""
|
193
|
-
|
194
|
-
res = await obj.client._request("get_value", node=node, tick=tick, nchain=99)
|
195
|
-
if not obj.meta:
|
196
|
-
res = res.value
|
197
|
-
yprint(res, stream=obj.stdout)
|
198
|
-
|
199
|
-
|
200
|
-
@cli.command()
|
201
|
-
@click.option("-n", "--num", type=int, help="Return at most this many IDs")
|
202
|
-
@click.option("-c", "--current", is_flag=True, help="Return only IDs with current data")
|
203
|
-
@click.option("-C", "--copy", is_flag=True, help="Create an no-op change")
|
204
|
-
@click.argument("node", nargs=1)
|
205
|
-
@click.pass_obj
|
206
|
-
async def enum(obj, node, num, current, copy):
|
207
|
-
"""
|
208
|
-
List IDs of live data by a specific node.
|
209
|
-
|
210
|
-
Can be used to determine whether a node still has live data,
|
211
|
-
otherwise it can be deleted.
|
212
|
-
|
213
|
-
If '--current' is set, only the IDs of the entries that have last been
|
214
|
-
updated by that node are shown. '--copy' rewrites these entries.
|
215
|
-
|
216
|
-
Increase verbosity to also show the oject paths.
|
217
|
-
"""
|
218
|
-
|
219
|
-
res = await obj.client._request("enum_node", node=node, max=num, current=current)
|
220
|
-
if obj.meta and not copy and obj.debug <= 1:
|
221
|
-
yprint(res, stream=obj.stdout)
|
222
|
-
else:
|
223
|
-
for k in res.result:
|
224
|
-
if copy or obj.debug > 1:
|
225
|
-
res = await obj.client._request("get_value", node=node, tick=k, nchain=3)
|
226
|
-
if obj.debug > 1:
|
227
|
-
print(k, res.path)
|
228
|
-
else:
|
229
|
-
print(k)
|
230
|
-
if copy and res.chain.node == node:
|
231
|
-
res = await obj.client.set(res.path, value=res.value, chain=res.chain)
|
232
|
-
else:
|
233
|
-
print(k)
|
234
|
-
|
235
|
-
|
236
|
-
@cli.command()
|
237
|
-
@click.argument("node", nargs=1)
|
238
|
-
@click.pass_obj
|
239
|
-
async def kill(obj, node):
|
240
|
-
"""
|
241
|
-
Remove a node from the node list.
|
242
|
-
|
243
|
-
This command only works if this node does not have any current data in
|
244
|
-
the system.
|
245
|
-
"""
|
246
|
-
res = await obj.client._request("kill_node", node=node)
|
247
|
-
if obj.meta:
|
248
|
-
yprint(res, stream=obj.stdout)
|
@@ -1,433 +0,0 @@
|
|
1
|
-
# command line interface
|
2
|
-
from __future__ import annotations
|
3
|
-
|
4
|
-
import sys
|
5
|
-
import time
|
6
|
-
from functools import partial
|
7
|
-
|
8
|
-
import anyio
|
9
|
-
import asyncclick as click
|
10
|
-
from moat.util import P, Path, attr_args, attrdict, process_args, yprint
|
11
|
-
|
12
|
-
from moat.kv.code import CodeRoot
|
13
|
-
from moat.kv.data import add_dates, data_get
|
14
|
-
from moat.kv.runner import AllRunnerRoot, AnyRunnerRoot, SingleRunnerRoot
|
15
|
-
|
16
|
-
|
17
|
-
@click.group() # pylint: disable=undefined-variable
|
18
|
-
@click.option("-n", "--node", help="node to run this code on. Empty: any one node, '-': all nodes")
|
19
|
-
@click.option("-g", "--group", help="group to run this code on. Empty: default")
|
20
|
-
@click.pass_context
|
21
|
-
async def cli(ctx, node, group):
|
22
|
-
"""Run code stored in MoaT-KV.
|
23
|
-
|
24
|
-
\b
|
25
|
-
The option '-n' is somewhat special:
|
26
|
-
-n - Jobs for all hosts
|
27
|
-
-n XXX Jobs for host XXX
|
28
|
-
(no -n) Jobs for any host
|
29
|
-
|
30
|
-
The default group is 'default'.
|
31
|
-
"""
|
32
|
-
obj = ctx.obj
|
33
|
-
if group is None:
|
34
|
-
group = "default"
|
35
|
-
if group == "-":
|
36
|
-
if node is not None:
|
37
|
-
raise click.UsageError("'-g -' doesn't make sense with '-n'")
|
38
|
-
if ctx.invoked_subcommand != "info":
|
39
|
-
raise click.UsageError("'-g -' only makes sense with the 'info' command")
|
40
|
-
obj.runner_root = SingleRunnerRoot
|
41
|
-
subpath = (None,)
|
42
|
-
elif not node:
|
43
|
-
obj.runner_root = AnyRunnerRoot
|
44
|
-
subpath = (group,)
|
45
|
-
elif node == "-":
|
46
|
-
obj.runner_root = AllRunnerRoot
|
47
|
-
subpath = (group,)
|
48
|
-
else:
|
49
|
-
obj.runner_root = SingleRunnerRoot
|
50
|
-
subpath = (node, group)
|
51
|
-
|
52
|
-
cfg = obj.cfg["kv"]["runner"]
|
53
|
-
obj.subpath = Path(cfg["sub"][obj.runner_root.SUB]) + subpath
|
54
|
-
obj.path = cfg["prefix"] + obj.subpath
|
55
|
-
obj.statepath = cfg["state"] + obj.subpath
|
56
|
-
|
57
|
-
|
58
|
-
@cli.group("at", short_help="path of the job to operate on", invoke_without_command=True)
|
59
|
-
@click.argument("path", nargs=1, type=P)
|
60
|
-
@click.pass_context
|
61
|
-
async def at_cli(ctx, path):
|
62
|
-
"""
|
63
|
-
Add, list, modify, delete jobs at/under this path.
|
64
|
-
"""
|
65
|
-
obj = ctx.obj
|
66
|
-
obj.jobpath = path
|
67
|
-
|
68
|
-
if ctx.invoked_subcommand is None:
|
69
|
-
res = await obj.client.get(obj.path + path, nchain=obj.meta)
|
70
|
-
yprint(
|
71
|
-
res if obj.meta else res.value if "value" in res else None,
|
72
|
-
stream=obj.stdout,
|
73
|
-
)
|
74
|
-
|
75
|
-
|
76
|
-
@cli.command("info")
|
77
|
-
@click.pass_obj
|
78
|
-
async def info_(obj):
|
79
|
-
"""
|
80
|
-
List available groups for the node in question.
|
81
|
-
|
82
|
-
\b
|
83
|
-
Options (between 'job' and 'info')
|
84
|
-
(none) list groups with jobs for any host
|
85
|
-
-n - list groups with jobs for all hosts
|
86
|
-
-g - list hosts that have specific jobs
|
87
|
-
-n XXX list groups with jobs for a specific host
|
88
|
-
"""
|
89
|
-
path = obj.path[:-1]
|
90
|
-
async for r in obj.client.get_tree(path=path, min_depth=1, max_depth=1, empty=True):
|
91
|
-
print(r.path[-1], file=obj.stdout)
|
92
|
-
|
93
|
-
|
94
|
-
@at_cli.command("--help", hidden=True)
|
95
|
-
@click.pass_context
|
96
|
-
def help_(ctx): # pylint:disable=unused-variable # oh boy
|
97
|
-
print(at_cli.get_help(ctx))
|
98
|
-
|
99
|
-
|
100
|
-
@at_cli.command("path")
|
101
|
-
@click.pass_obj
|
102
|
-
async def path__(obj):
|
103
|
-
"""
|
104
|
-
Emit the full path leading to the specified runner object.
|
105
|
-
|
106
|
-
Useful for copying or for state monitoring.
|
107
|
-
|
108
|
-
NEVER directly write to the state object. It's controlled by the
|
109
|
-
runner. You'll confuse it if you do that.
|
110
|
-
|
111
|
-
Updating the control object will cancel any running code.
|
112
|
-
"""
|
113
|
-
path = obj.jobpath
|
114
|
-
res = dict(command=obj.path + path, state=obj.statepath + path)
|
115
|
-
yprint(res, stream=obj.stdout)
|
116
|
-
|
117
|
-
|
118
|
-
@cli.command("run")
|
119
|
-
@click.option(
|
120
|
-
"-n",
|
121
|
-
"--nodes",
|
122
|
-
type=int,
|
123
|
-
default=0,
|
124
|
-
help="Size of the group (not for single-node runners)",
|
125
|
-
)
|
126
|
-
@click.pass_obj
|
127
|
-
async def run(obj, nodes):
|
128
|
-
"""
|
129
|
-
Run code that needs to run.
|
130
|
-
|
131
|
-
This does not return.
|
132
|
-
"""
|
133
|
-
from moat.util import as_service
|
134
|
-
|
135
|
-
if obj.subpath[-1] == "-":
|
136
|
-
raise click.UsageError("Group '-' can only be used for listing.")
|
137
|
-
if nodes and obj.runner_root is SingleRunnerRoot:
|
138
|
-
raise click.UsageError("A single-site runner doesn't have a size.")
|
139
|
-
|
140
|
-
async with as_service(obj) as evt:
|
141
|
-
c = obj.client
|
142
|
-
cr = await CodeRoot.as_handler(c)
|
143
|
-
await obj.runner_root.as_handler(
|
144
|
-
c,
|
145
|
-
subpath=obj.subpath,
|
146
|
-
code=cr,
|
147
|
-
**({"nodes": nodes} if nodes else {}),
|
148
|
-
)
|
149
|
-
evt.set()
|
150
|
-
await anyio.sleep_forever()
|
151
|
-
|
152
|
-
|
153
|
-
async def _state_fix(obj, state, state_only, path, r):
|
154
|
-
try:
|
155
|
-
val = r.value
|
156
|
-
except AttributeError:
|
157
|
-
return
|
158
|
-
if state:
|
159
|
-
rs = await obj.client._request(
|
160
|
-
action="get_value",
|
161
|
-
path=state + r.path,
|
162
|
-
iter=False,
|
163
|
-
nchain=obj.meta,
|
164
|
-
)
|
165
|
-
if state_only:
|
166
|
-
r.value = rs
|
167
|
-
else:
|
168
|
-
if obj.meta:
|
169
|
-
val["state"] = rs
|
170
|
-
elif "value" in rs:
|
171
|
-
val["state"] = rs.value
|
172
|
-
if "value" in rs:
|
173
|
-
add_dates(rs.value)
|
174
|
-
if not state_only:
|
175
|
-
if path:
|
176
|
-
r.path = path + r.path
|
177
|
-
add_dates(val)
|
178
|
-
|
179
|
-
return r
|
180
|
-
|
181
|
-
|
182
|
-
@at_cli.command("list")
|
183
|
-
@click.option("-s", "--state", is_flag=True, help="Add state data")
|
184
|
-
@click.option("-S", "--state-only", is_flag=True, help="Only output state data")
|
185
|
-
@click.option("-t", "--table", is_flag=True, help="one-line output")
|
186
|
-
@click.option(
|
187
|
-
"-d",
|
188
|
-
"--as-dict",
|
189
|
-
default=None,
|
190
|
-
help="Structure as dictionary. The argument is the key to use "
|
191
|
-
"for values. Default: return as list",
|
192
|
-
)
|
193
|
-
@click.pass_obj
|
194
|
-
async def list_(obj, state, state_only, table, as_dict):
|
195
|
-
"""List run entries."""
|
196
|
-
if table and state:
|
197
|
-
raise click.UsageError("'--table' and '--state' are mutually exclusive")
|
198
|
-
|
199
|
-
path = obj.jobpath
|
200
|
-
|
201
|
-
if state or state_only or table:
|
202
|
-
state = obj.statepath + path
|
203
|
-
|
204
|
-
if table:
|
205
|
-
from moat.kv.errors import ErrorRoot
|
206
|
-
|
207
|
-
err = await ErrorRoot.as_handler(obj.client)
|
208
|
-
|
209
|
-
async for r in obj.client.get_tree(obj.path + path):
|
210
|
-
p = path + r.path
|
211
|
-
s = await obj.client.get(state + r.path)
|
212
|
-
if "value" not in s:
|
213
|
-
st = "-never-"
|
214
|
-
elif s.value.started > s.value.stopped:
|
215
|
-
st = s.value.node
|
216
|
-
else:
|
217
|
-
try:
|
218
|
-
e = await err.get_error_record("run", obj.path + p, create=False)
|
219
|
-
except KeyError:
|
220
|
-
st = "-stopped-"
|
221
|
-
else:
|
222
|
-
if e is None or e.resolved:
|
223
|
-
st = "-stopped-"
|
224
|
-
else:
|
225
|
-
st = " | ".join(
|
226
|
-
"%s %s"
|
227
|
-
% (
|
228
|
-
Path.build(e.subpath)
|
229
|
-
if e._path[-2] == ee._path[-1]
|
230
|
-
else Path.build(ee.subpath),
|
231
|
-
getattr(ee, "message", None)
|
232
|
-
or getattr(ee, "comment", None)
|
233
|
-
or "-",
|
234
|
-
)
|
235
|
-
for ee in e
|
236
|
-
)
|
237
|
-
print(p, r.value.code, st, file=obj.stdout)
|
238
|
-
|
239
|
-
else:
|
240
|
-
await data_get(
|
241
|
-
obj,
|
242
|
-
obj.path + path,
|
243
|
-
as_dict=as_dict,
|
244
|
-
item_mangle=partial(_state_fix, obj, state, state_only, None if as_dict else path),
|
245
|
-
)
|
246
|
-
|
247
|
-
|
248
|
-
@at_cli.command("state")
|
249
|
-
@click.option("-r", "--result", is_flag=True, help="Just print the actual result.")
|
250
|
-
@click.pass_obj
|
251
|
-
async def state_(obj, result):
|
252
|
-
"""Get the status of a runner entry."""
|
253
|
-
path = obj.jobpath
|
254
|
-
|
255
|
-
if obj.subpath[-1] == "-":
|
256
|
-
raise click.UsageError("Group '-' can only be used for listing.")
|
257
|
-
if result and obj.meta:
|
258
|
-
raise click.UsageError("You can't use '-v' and '-r' at the same time.")
|
259
|
-
if not len(path):
|
260
|
-
raise click.UsageError("You need a non-empty path.")
|
261
|
-
path = obj.statepath + obj.jobpath
|
262
|
-
|
263
|
-
res = await obj.client.get(path, nchain=obj.meta)
|
264
|
-
if "value" not in res:
|
265
|
-
if obj.debug:
|
266
|
-
print("Not found (yet?)", file=sys.stderr)
|
267
|
-
sys.exit(1)
|
268
|
-
|
269
|
-
add_dates(res.value)
|
270
|
-
if not obj.meta:
|
271
|
-
res = res.value
|
272
|
-
yprint(res, stream=obj.stdout)
|
273
|
-
|
274
|
-
|
275
|
-
@at_cli.command()
|
276
|
-
@click.option("-s", "--state", is_flag=True, help="Add state data")
|
277
|
-
@click.pass_obj
|
278
|
-
async def get(obj, state):
|
279
|
-
"""Read a runner entry"""
|
280
|
-
path = obj.jobpath
|
281
|
-
if obj.subpath[-1] == "-":
|
282
|
-
raise click.UsageError("Group '-' can only be used for listing.")
|
283
|
-
if not path:
|
284
|
-
raise click.UsageError("You need a non-empty path.")
|
285
|
-
|
286
|
-
res = await obj.client._request(
|
287
|
-
action="get_value",
|
288
|
-
path=obj.path + path,
|
289
|
-
iter=False,
|
290
|
-
nchain=obj.meta,
|
291
|
-
)
|
292
|
-
if "value" not in res:
|
293
|
-
print("Not found.", file=sys.stderr)
|
294
|
-
return
|
295
|
-
res.path = path
|
296
|
-
if state:
|
297
|
-
state = obj.statepath
|
298
|
-
await _state_fix(obj, state, False, path, res)
|
299
|
-
if not obj.meta:
|
300
|
-
res = res.value
|
301
|
-
|
302
|
-
yprint(res, stream=obj.stdout)
|
303
|
-
|
304
|
-
|
305
|
-
@at_cli.command()
|
306
|
-
@click.option("-f", "--force", is_flag=True, help="Force deletion even if messy")
|
307
|
-
@click.pass_obj
|
308
|
-
async def delete(obj, force):
|
309
|
-
"""Remove a runner entry"""
|
310
|
-
path = obj.jobpath
|
311
|
-
|
312
|
-
if obj.subpath[-1] == "-":
|
313
|
-
raise click.UsageError("Group '-' can only be used for listing.")
|
314
|
-
if not path:
|
315
|
-
raise click.UsageError("You need a non-empty path.")
|
316
|
-
|
317
|
-
res = await obj.client.get(obj.path + path, nchain=3)
|
318
|
-
if "value" not in res:
|
319
|
-
res.info = "Does not exist."
|
320
|
-
else:
|
321
|
-
val = res.value
|
322
|
-
if "target" not in val:
|
323
|
-
val.target = None
|
324
|
-
if val.target is not None:
|
325
|
-
val.target = None
|
326
|
-
res = await obj.client.set(obj.path + path, value=val, nchain=3, chain=res.chain)
|
327
|
-
if not force:
|
328
|
-
res.info = "'target' was set: cleared but not deleted."
|
329
|
-
if force or val.target is None:
|
330
|
-
sres = await obj.client.get(obj.statepath + path, nchain=3)
|
331
|
-
if not force and "value" in sres and sres.value.stopped < sres.value.started:
|
332
|
-
res.info = "Still running, not deleted."
|
333
|
-
else:
|
334
|
-
sres = await obj.client.delete(obj.statepath + path, chain=sres.chain)
|
335
|
-
res = await obj.client.delete(obj.path + path, chain=res.chain)
|
336
|
-
if "value" in res and res.value.stopped < res.value.started:
|
337
|
-
res.info = "Deleted (unclean!)."
|
338
|
-
else:
|
339
|
-
res.info = "Deleted."
|
340
|
-
|
341
|
-
if obj.meta:
|
342
|
-
yprint(res, stream=obj.stdout)
|
343
|
-
elif obj.debug:
|
344
|
-
print(res.info)
|
345
|
-
|
346
|
-
|
347
|
-
@at_cli.command("set")
|
348
|
-
@click.option("-c", "--code", help="Path to the code that should run.")
|
349
|
-
@click.option("-C", "--copy", help="Use this entry as a template.")
|
350
|
-
@click.option("-t", "--time", "tm", help="time the code should next run at. '-':not")
|
351
|
-
@click.option("-r", "--repeat", type=int, help="Seconds the code should re-run after")
|
352
|
-
@click.option("-k", "--ok", type=float, help="Code is OK if it ran this many seconds")
|
353
|
-
@click.option("-b", "--backoff", type=float, help="Back-off factor. Default: 1.4")
|
354
|
-
@click.option("-d", "--delay", type=int, help="Seconds the code should retry after (w/ backoff)")
|
355
|
-
@click.option("-i", "--info", help="Short human-readable information")
|
356
|
-
@attr_args
|
357
|
-
@click.pass_obj
|
358
|
-
async def set_(obj, code, tm, info, ok, repeat, delay, backoff, copy, **kw):
|
359
|
-
"""Add or modify a runner.
|
360
|
-
|
361
|
-
Code typically requires some input parameters.
|
362
|
-
|
363
|
-
You should use '-v NAME VALUE' for string values, '-p NAME VALUE' for
|
364
|
-
paths, and '-e NAME VALUE' for other data. '-e NAME -' deletes an item.
|
365
|
-
"""
|
366
|
-
path = obj.jobpath
|
367
|
-
|
368
|
-
if obj.subpath[-1] == "-":
|
369
|
-
raise click.UsageError("Group '-' can only be used for listing.")
|
370
|
-
|
371
|
-
if code is not None:
|
372
|
-
code = P(code)
|
373
|
-
if copy:
|
374
|
-
copy = P(copy)
|
375
|
-
path = obj.path + P(path)
|
376
|
-
|
377
|
-
res = await obj.client._request(action="get_value", path=copy or path, iter=False, nchain=3)
|
378
|
-
if "value" not in res:
|
379
|
-
if copy:
|
380
|
-
raise click.UsageError("--copy: use the complete path to an existing entry")
|
381
|
-
elif code is None:
|
382
|
-
raise click.UsageError("New entry, need code")
|
383
|
-
res = {}
|
384
|
-
chain = None
|
385
|
-
else:
|
386
|
-
chain = None if copy else res["chain"]
|
387
|
-
res = res["value"]
|
388
|
-
if copy and "code" not in res:
|
389
|
-
raise click.UsageError("'--copy' needs a runner entry")
|
390
|
-
|
391
|
-
vl = attrdict(**res.setdefault("data", {}))
|
392
|
-
vl = process_args(vl, **kw)
|
393
|
-
res["data"] = vl
|
394
|
-
|
395
|
-
if code is not None:
|
396
|
-
res["code"] = code
|
397
|
-
if ok is not None:
|
398
|
-
res["ok_after"] = ok
|
399
|
-
if info is not None:
|
400
|
-
res["info"] = info
|
401
|
-
if backoff is not None:
|
402
|
-
res["backoff"] = backoff
|
403
|
-
if delay is not None:
|
404
|
-
res["delay"] = delay
|
405
|
-
if repeat is not None:
|
406
|
-
res["repeat"] = repeat
|
407
|
-
if tm is not None:
|
408
|
-
if tm == "-":
|
409
|
-
res["target"] = None
|
410
|
-
else:
|
411
|
-
res["target"] = time.time() + float(tm)
|
412
|
-
|
413
|
-
res = await obj.client.set(path, value=res, nchain=3, chain=chain)
|
414
|
-
if obj.meta:
|
415
|
-
yprint(res, stream=obj.stdout)
|
416
|
-
|
417
|
-
|
418
|
-
@cli.command(short_help="Show runners' keepalive messages")
|
419
|
-
@click.pass_obj
|
420
|
-
async def monitor(obj):
|
421
|
-
"""
|
422
|
-
Runners periodically send a keepalive message. Show them.
|
423
|
-
"""
|
424
|
-
|
425
|
-
# TODO this does not watch changes in MoaT-KV.
|
426
|
-
# It also should watch individual jobs' state changes.
|
427
|
-
if obj.subpath[-1] == "-":
|
428
|
-
raise click.UsageError("Group '-' can only be used for listing.")
|
429
|
-
|
430
|
-
async with obj.client.msg_monitor("run") as cl:
|
431
|
-
async for msg in cl:
|
432
|
-
yprint(msg, stream=obj.stdout)
|
433
|
-
print("---", file=obj.stdout)
|