moat-kv 0.70.24__py3-none-any.whl → 0.71.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- moat/kv/__init__.py +6 -7
- moat/kv/_cfg.yaml +5 -8
- 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 -8
- 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 +29 -29
- 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 +28 -32
- 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 +8 -6
- {moat_kv-0.70.24.dist-info → moat_kv-0.71.6.dist-info}/METADATA +7 -6
- moat_kv-0.71.6.dist-info/RECORD +47 -0
- {moat_kv-0.70.24.dist-info → moat_kv-0.71.6.dist-info}/WHEEL +1 -1
- moat_kv-0.71.6.dist-info/licenses/LICENSE +3 -0
- moat_kv-0.71.6.dist-info/licenses/LICENSE.APACHE2 +202 -0
- moat_kv-0.71.6.dist-info/licenses/LICENSE.MIT +20 -0
- moat_kv-0.71.6.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 -97
- 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 -74
- build/lib/moat/kv/backend/serf.py +0 -45
- 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/serf.py +0 -250
- 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
- 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 -97
- 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 -74
- debian/moat-kv/usr/lib/python3/dist-packages/moat/kv/backend/serf.py +0 -45
- 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/serf.py +0 -250
- 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
- docs/source/conf.py +0 -201
- examples/pathify.py +0 -45
- moat/kv/backend/serf.py +0 -45
- moat/kv/codec.py +0 -11
- moat/kv/mock/serf.py +0 -250
- moat_kv-0.70.24.dist-info/RECORD +0 -137
- moat_kv-0.70.24.dist-info/top_level.txt +0 -9
- {moat_kv-0.70.24.dist-info → moat_kv-0.71.6.dist-info}/licenses/LICENSE.txt +0 -0
build/lib/moat/kv/errors.py
DELETED
@@ -1,561 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
This module implements a way to store error messages in MoaT-KV,
|
3
|
-
and of course to remove or disable them when the error is gone.
|
4
|
-
|
5
|
-
Errors are implemented by storing relevant information at ``(*PREFIX,node,tock)``.
|
6
|
-
The prefix defaults to ``("error",)``; subsystems may want to create their
|
7
|
-
own list.
|
8
|
-
|
9
|
-
Each error is stored as a record with these arguments:
|
10
|
-
|
11
|
-
* path
|
12
|
-
|
13
|
-
The path to the element that caused the error.
|
14
|
-
|
15
|
-
* subsystem
|
16
|
-
|
17
|
-
A subsystem.
|
18
|
-
|
19
|
-
* severity
|
20
|
-
|
21
|
-
0…7 corresponding to fatal/error/warning/info/note/debug/trace.
|
22
|
-
|
23
|
-
* resolved
|
24
|
-
|
25
|
-
If set, contains the ``tock`` value when the problem was fixed.
|
26
|
-
|
27
|
-
* created
|
28
|
-
|
29
|
-
The timestamp (unixtime) when the problem first occurred.
|
30
|
-
|
31
|
-
* count
|
32
|
-
|
33
|
-
The number of occurrences.
|
34
|
-
|
35
|
-
* first_seen
|
36
|
-
|
37
|
-
The timestamp (unixtime) when the problem first occurred. Must not be
|
38
|
-
modified; used for strict unique-ification.
|
39
|
-
|
40
|
-
* last_seen
|
41
|
-
|
42
|
-
The timestamp (unixtime) when the problem last occurred.
|
43
|
-
|
44
|
-
(path,subsystem) must be unique.
|
45
|
-
|
46
|
-
|
47
|
-
Single error trace records may be stored below the error message. Their
|
48
|
-
name should be the node which noticed the problem, they have the following
|
49
|
-
structure:
|
50
|
-
|
51
|
-
* seen
|
52
|
-
|
53
|
-
Timestamp
|
54
|
-
|
55
|
-
* tock
|
56
|
-
|
57
|
-
Tock value when the problem occurred
|
58
|
-
|
59
|
-
* trace
|
60
|
-
|
61
|
-
A multi-line textual error message
|
62
|
-
|
63
|
-
* comment
|
64
|
-
|
65
|
-
The ``repr`` of the error, or an explicit commenting text.
|
66
|
-
|
67
|
-
* message
|
68
|
-
|
69
|
-
Some textual explanation of the error.
|
70
|
-
|
71
|
-
* data
|
72
|
-
|
73
|
-
Any additional data required to reproduce the problem; e.g. if a
|
74
|
-
stored procedure triggered an exception, the location of the actual code
|
75
|
-
and the parameters used when invoking it.
|
76
|
-
|
77
|
-
The message text must be fixed. It must be format-able using the "data"
|
78
|
-
of this record.
|
79
|
-
|
80
|
-
"""
|
81
|
-
|
82
|
-
from __future__ import annotations
|
83
|
-
|
84
|
-
import logging
|
85
|
-
import traceback
|
86
|
-
from collections import defaultdict
|
87
|
-
from time import time # wall clock, intentionally
|
88
|
-
from weakref import WeakValueDictionary
|
89
|
-
|
90
|
-
import anyio
|
91
|
-
from moat.util import Cache, NotGiven, Path
|
92
|
-
|
93
|
-
from .codec import packer
|
94
|
-
from .exceptions import ServerError
|
95
|
-
from .obj import AttrClientEntry, ClientEntry, ClientRoot
|
96
|
-
|
97
|
-
logger = logging.getLogger(__name__)
|
98
|
-
|
99
|
-
|
100
|
-
try:
|
101
|
-
ClosedResourceError = anyio.exceptions.ClosedResourceError
|
102
|
-
except AttributeError:
|
103
|
-
ClosedResourceError = anyio.ClosedResourceError
|
104
|
-
|
105
|
-
|
106
|
-
class ErrorSubEntry(AttrClientEntry):
|
107
|
-
"""
|
108
|
-
Tracks the latest occurrence of an error, per node.
|
109
|
-
"""
|
110
|
-
|
111
|
-
ATTRS = "seen tock trace str data comment message".split()
|
112
|
-
|
113
|
-
def child_type(self, name): # pylint: disable=arguments-differ
|
114
|
-
logger.warning("Unknown entry type at %r: %s", self._path, name)
|
115
|
-
return ClientEntry
|
116
|
-
|
117
|
-
def __repr__(self):
|
118
|
-
return "‹%s %s %d›" % (
|
119
|
-
self.__class__.__name__,
|
120
|
-
self._path[-3:],
|
121
|
-
getattr(self, "tock", -1),
|
122
|
-
)
|
123
|
-
|
124
|
-
async def set_value(self, value):
|
125
|
-
await super().set_value(value)
|
126
|
-
if value is NotGiven:
|
127
|
-
p = self.parent
|
128
|
-
if p is not None:
|
129
|
-
await self.parent.check_move()
|
130
|
-
|
131
|
-
|
132
|
-
class ErrorEntry(AttrClientEntry):
|
133
|
-
"""
|
134
|
-
A specific error. While it's recorded per node+tock, for uniqueness,
|
135
|
-
the error is really unique per subsystem and path.
|
136
|
-
"""
|
137
|
-
|
138
|
-
ATTRS = "path subsystem severity resolved created count first_seen last_seen message".split()
|
139
|
-
resolved = None # bool; if None, no details yet
|
140
|
-
created = None
|
141
|
-
count = 0
|
142
|
-
subsystem = None
|
143
|
-
first_seen = None
|
144
|
-
last_seen = None
|
145
|
-
path = ()
|
146
|
-
_real_entry = None
|
147
|
-
|
148
|
-
def __init__(self, *a, **kw):
|
149
|
-
super().__init__(*a, **kw)
|
150
|
-
self.node, self.tock = self.subpath[-2:]
|
151
|
-
|
152
|
-
def __repr__(self):
|
153
|
-
return "<%s: %s %s: %s>" % (
|
154
|
-
self.__class__.__name__,
|
155
|
-
"/".join(str(x) for x in self.subpath),
|
156
|
-
self.subsystem,
|
157
|
-
" ".join(str(x) for x in self.path),
|
158
|
-
)
|
159
|
-
|
160
|
-
@classmethod
|
161
|
-
def child_type(cls, name):
|
162
|
-
return ErrorSubEntry
|
163
|
-
|
164
|
-
@property
|
165
|
-
def real_entry(self):
|
166
|
-
while self._real_entry is not None:
|
167
|
-
self = self._real_entry # pylint: disable=self-cls-assignment
|
168
|
-
return self
|
169
|
-
|
170
|
-
async def check_move(self):
|
171
|
-
dest = self.real_entry
|
172
|
-
if dest is not self:
|
173
|
-
await self.root.add_clean(self)
|
174
|
-
|
175
|
-
async def resolve(self):
|
176
|
-
"""
|
177
|
-
Record that this error has been resolved.
|
178
|
-
"""
|
179
|
-
self.resolved = True
|
180
|
-
await self.save()
|
181
|
-
|
182
|
-
async def add_exc(self, node, exc, data, comment=None, message=None):
|
183
|
-
"""
|
184
|
-
Store a detail record for this error.
|
185
|
-
|
186
|
-
One per node, so we don't try to avoid collisions.
|
187
|
-
|
188
|
-
Arguments:
|
189
|
-
node (str): The node the error occurred in.
|
190
|
-
exc (Exception): The actual exception
|
191
|
-
data (dict): any relevant data to reproduce the problem.
|
192
|
-
"""
|
193
|
-
res: ErrorSubEntry = self.get(node)
|
194
|
-
if res is None:
|
195
|
-
res = self.allocate(node)
|
196
|
-
if True: # pylint: disable=using-constant-test # just for the pylint block
|
197
|
-
# pylint: disable=attribute-defined-outside-init
|
198
|
-
res.seen = time()
|
199
|
-
res.tock = await self.root.client.get_tock()
|
200
|
-
res.comment = comment or repr(exc)
|
201
|
-
|
202
|
-
if exc is not None:
|
203
|
-
t = traceback.format_exception(type(exc), exc, exc.__traceback__)
|
204
|
-
if len(t) > 40:
|
205
|
-
t = t[:10] + ["…\n"] + t[:30]
|
206
|
-
res.trace = "".join(t) # pylint: disable=attribute-defined-outside-init
|
207
|
-
if message is not None:
|
208
|
-
res.message = message # pylint: disable=attribute-defined-outside-init
|
209
|
-
if data is not None:
|
210
|
-
res.data = data # pylint: disable=attribute-defined-outside-init
|
211
|
-
|
212
|
-
if message:
|
213
|
-
if data:
|
214
|
-
try:
|
215
|
-
m = message.format(exc=exc, **data)
|
216
|
-
except Exception as exc: # pylint: disable=unused-argument # OH COME ON
|
217
|
-
m = message + f" (FORMAT {exc!r})"
|
218
|
-
else:
|
219
|
-
m = message
|
220
|
-
if m:
|
221
|
-
if exc:
|
222
|
-
m += f": {exc!r}"
|
223
|
-
elif exc:
|
224
|
-
m = repr(exc)
|
225
|
-
elif comment:
|
226
|
-
m = comment
|
227
|
-
elif data:
|
228
|
-
m = repr(data)
|
229
|
-
logger.warning("Error %r %s: %s", self._path, node, m)
|
230
|
-
|
231
|
-
try:
|
232
|
-
r = await res.save()
|
233
|
-
except TypeError:
|
234
|
-
for k in res.ATTRS:
|
235
|
-
v = getattr(res, k, None)
|
236
|
-
try:
|
237
|
-
packer(v)
|
238
|
-
except TypeError:
|
239
|
-
setattr(res, k, repr(v))
|
240
|
-
r = await res.save()
|
241
|
-
return r
|
242
|
-
|
243
|
-
async def add_comment(self, node, comment, data):
|
244
|
-
"""
|
245
|
-
Store this comment, typically used when something resumes working.
|
246
|
-
One per node, so we don't need to avoid collisions.
|
247
|
-
"""
|
248
|
-
res = dict(
|
249
|
-
seen=time(),
|
250
|
-
tock=await self.root.client.get_tock(),
|
251
|
-
comment=comment,
|
252
|
-
data=data,
|
253
|
-
)
|
254
|
-
logger.info("Comment %s: %s", node, comment)
|
255
|
-
await self.root.client.set(self._path, chain=self.chain, value=res)
|
256
|
-
|
257
|
-
async def delete(self): # pylint: disable=signature-differs,arguments-differ
|
258
|
-
"""
|
259
|
-
Delete myself from storage.
|
260
|
-
|
261
|
-
This doesn't do anything locally, the watcher will get it.
|
262
|
-
"""
|
263
|
-
await self.root._pop(self)
|
264
|
-
try:
|
265
|
-
return await super().delete(chain=False)
|
266
|
-
except ServerError as exc:
|
267
|
-
if "is new" not in repr(exc):
|
268
|
-
raise
|
269
|
-
|
270
|
-
async def move_to_real(self):
|
271
|
-
"""
|
272
|
-
Move this entry, or rather the errors in it, to another.
|
273
|
-
|
274
|
-
This is used for collision resolution.
|
275
|
-
"""
|
276
|
-
|
277
|
-
dest = self.real_entry
|
278
|
-
# logger.warning("DEL 1 %r %r",self,dest)
|
279
|
-
assert dest is not self, self
|
280
|
-
kid = self.get(self.root.name)
|
281
|
-
if kid is not None:
|
282
|
-
dkid = dest.get(self.root.name)
|
283
|
-
# logger.warning("DEL 2 %r %r %r %r",self,dest,kid,dkid)
|
284
|
-
val = kid.get_value()
|
285
|
-
if dkid is None:
|
286
|
-
dkid = dest.allocate(self.root.name)
|
287
|
-
await dkid.set_value(val)
|
288
|
-
await dkid.save()
|
289
|
-
elif getattr(dkid, "tock", 0) < getattr(kid, "tock", 0):
|
290
|
-
await dkid.set_value(val)
|
291
|
-
await dkid.save()
|
292
|
-
# logger.warning("DEL 3 %r %r %r",self,dest,dkid)
|
293
|
-
await kid.delete()
|
294
|
-
if not len(self):
|
295
|
-
# logger.warning("DEL 4 %r",self)
|
296
|
-
await self.delete()
|
297
|
-
# logger.warning("DEL 5 %r",self)
|
298
|
-
|
299
|
-
async def set_value(self, value):
|
300
|
-
"""Overridden: set_value
|
301
|
-
|
302
|
-
Stores a pointer to the error in the root and keeps the records unique
|
303
|
-
"""
|
304
|
-
|
305
|
-
await self.root._pop(self)
|
306
|
-
if value is NotGiven:
|
307
|
-
if self.value is NotGiven:
|
308
|
-
return
|
309
|
-
keep = await self.root.get_error_record(self.subsystem, self.path, create=False)
|
310
|
-
if keep is not None:
|
311
|
-
self._real_entry = keep.real_entry
|
312
|
-
await self.move_to_real()
|
313
|
-
await super().set_value(value)
|
314
|
-
return
|
315
|
-
|
316
|
-
await super().set_value(value)
|
317
|
-
|
318
|
-
drop, keep = await self.root._unique(self)
|
319
|
-
# logger.debug("UNIQ %r %r %r %s/%s",self,drop,keep,
|
320
|
-
# "x" if drop is None else drop.subpath[0],self.root.name)
|
321
|
-
if drop is not None:
|
322
|
-
# self.root._dropped(drop)
|
323
|
-
drop._real_entry = keep
|
324
|
-
|
325
|
-
if drop.subpath[0] == self.root.name:
|
326
|
-
await drop.move_to_real()
|
327
|
-
# TODO remember to do it anyway, after next tick and when we're it
|
328
|
-
self.root._push(self.real_entry)
|
329
|
-
|
330
|
-
|
331
|
-
class ErrorStep(ClientEntry):
|
332
|
-
"""
|
333
|
-
Errors are stored at /tock/node; this represents the /tock part
|
334
|
-
"""
|
335
|
-
|
336
|
-
@classmethod
|
337
|
-
def child_type(cls, name):
|
338
|
-
return ErrorEntry
|
339
|
-
|
340
|
-
|
341
|
-
def _defaultdict_init(*a, **k):
|
342
|
-
return defaultdict(_defaultdict_init, *a, **k)
|
343
|
-
|
344
|
-
|
345
|
-
class ErrorRoot(ClientRoot):
|
346
|
-
"""
|
347
|
-
This class represents the root of an error handling hierarchy. Ideally
|
348
|
-
there should only be one, but you can use more if necessary.
|
349
|
-
|
350
|
-
You typically don't create this class directly; instead, call
|
351
|
-
:meth:`ClientRoot.as_handler`::
|
352
|
-
|
353
|
-
errs = await ErrorRoot.as_handler(client, your_config.get("error-handler",{})
|
354
|
-
|
355
|
-
Configuration:
|
356
|
-
|
357
|
-
Arguments:
|
358
|
-
prefix (list): Where to store the error data in MoaT-KV.
|
359
|
-
The default is ``('.moat','kv','error')``.
|
360
|
-
"""
|
361
|
-
|
362
|
-
CFG = "errors"
|
363
|
-
|
364
|
-
def __init__(self, *a, name=None, **kw):
|
365
|
-
super().__init__(*a, **kw)
|
366
|
-
self.name = name or self.client.client_name
|
367
|
-
self._loaded = anyio.Event()
|
368
|
-
self._errors = defaultdict(dict) # node > tock > Entry
|
369
|
-
self._active = defaultdict(dict) # subsystem > path > Entry
|
370
|
-
self._done = defaultdict(WeakValueDictionary) # subsystem > path > Entry
|
371
|
-
self._latest = Cache(100)
|
372
|
-
self._to_clean = set()
|
373
|
-
|
374
|
-
@classmethod
|
375
|
-
def child_type(cls, name):
|
376
|
-
return ErrorStep
|
377
|
-
|
378
|
-
async def add_clean(self, entry):
|
379
|
-
# self._to_clean.add(entry)
|
380
|
-
pass
|
381
|
-
# TODO run cleanup code that consolidates these errors when we get a TagMessage
|
382
|
-
# TODO use a weakset
|
383
|
-
|
384
|
-
def all_errors(self, subsystem=None):
|
385
|
-
"""
|
386
|
-
Iterate over all active errors, either a single subsystem or all of
|
387
|
-
them.
|
388
|
-
|
389
|
-
Arguments:
|
390
|
-
subsystem (str): The subsystem to filter for.
|
391
|
-
"""
|
392
|
-
|
393
|
-
if subsystem is None:
|
394
|
-
for s in list(self._active.values()):
|
395
|
-
yield from iter(s.values())
|
396
|
-
else:
|
397
|
-
yield from iter(self._active[subsystem].values())
|
398
|
-
|
399
|
-
async def get_error_record(self, subsystem, path, *, create=True):
|
400
|
-
"""Retrieve or generate an error record for a particular subsystem
|
401
|
-
and path.
|
402
|
-
|
403
|
-
If ``create`` is set, the record may be incomplete and
|
404
|
-
must be filled and stored by the caller.
|
405
|
-
"""
|
406
|
-
|
407
|
-
err = self._active[subsystem].get(path, None)
|
408
|
-
if err is not None:
|
409
|
-
return err
|
410
|
-
err = self._done[subsystem].get(path, None)
|
411
|
-
if err is not None:
|
412
|
-
return err
|
413
|
-
if not create:
|
414
|
-
return None
|
415
|
-
tock = await self.client.get_tock()
|
416
|
-
return self.follow(Path(self.name, tock), create=True)
|
417
|
-
|
418
|
-
async def _unique(self, entry):
|
419
|
-
"""
|
420
|
-
Test whether this record is unique.
|
421
|
-
|
422
|
-
Returns:
|
423
|
-
``None,None`` if there is no problem
|
424
|
-
Otherwise, a tuple:
|
425
|
-
- the record that should be deleted
|
426
|
-
- the record that should be kept
|
427
|
-
|
428
|
-
This is used for collision resolution and **must** be stable, i.e.
|
429
|
-
not depend on which node it is running on or which entry arrives
|
430
|
-
first.
|
431
|
-
"""
|
432
|
-
other = await self.get_error_record(entry.subsystem, entry.path, create=False)
|
433
|
-
if other is None or other is entry:
|
434
|
-
return None, None
|
435
|
-
|
436
|
-
def _n(x):
|
437
|
-
return 99999999999999 if x is None else x
|
438
|
-
|
439
|
-
if _n(entry.first_seen) < _n(other.first_seen):
|
440
|
-
return other, entry
|
441
|
-
elif _n(other.first_seen) < _n(entry.first_seen):
|
442
|
-
return entry, other
|
443
|
-
|
444
|
-
if entry.node < other.node:
|
445
|
-
return other, entry
|
446
|
-
elif other.node < entry.node:
|
447
|
-
return entry, other
|
448
|
-
|
449
|
-
raise RuntimeError(f"This cannot happen: {entry.node} {entry.tock}")
|
450
|
-
|
451
|
-
async def record_working( # pylint: disable=dangerous-default-value
|
452
|
-
self,
|
453
|
-
subsystem,
|
454
|
-
path,
|
455
|
-
*,
|
456
|
-
comment=None,
|
457
|
-
data={},
|
458
|
-
force=False,
|
459
|
-
):
|
460
|
-
"""This exception has been fixed.
|
461
|
-
|
462
|
-
Arguments:
|
463
|
-
subsystem (str): The subsystem with the error.
|
464
|
-
*path: the path to the no-longer-offending entry.
|
465
|
-
comment (str): text to enter
|
466
|
-
data (dict): any relevant data
|
467
|
-
force (bool): create an entry even if no error is open.
|
468
|
-
"""
|
469
|
-
rec = await self.get_error_record(subsystem, path, create=force)
|
470
|
-
if rec is None:
|
471
|
-
return
|
472
|
-
if not rec.resolved:
|
473
|
-
rec.resolved = time()
|
474
|
-
await rec.save()
|
475
|
-
if comment or data:
|
476
|
-
await rec.real_entry.add_comment(self.name, comment, data)
|
477
|
-
return rec
|
478
|
-
|
479
|
-
async def record_error( # pylint: disable=dangerous-default-value
|
480
|
-
self,
|
481
|
-
subsystem,
|
482
|
-
path,
|
483
|
-
*,
|
484
|
-
exc=None,
|
485
|
-
data={},
|
486
|
-
severity=0,
|
487
|
-
message=None,
|
488
|
-
force: bool = False,
|
489
|
-
comment: str = None,
|
490
|
-
):
|
491
|
-
"""An exception has occurred for this subtype and path.
|
492
|
-
|
493
|
-
Arguments:
|
494
|
-
subsystem (str): The subsystem with the error.
|
495
|
-
*path: the path to the no-longer-offending entry.
|
496
|
-
exc (Exception): The exception in question.
|
497
|
-
data (dict): any relevant data
|
498
|
-
severity (int): error gravity.
|
499
|
-
force (bool): Flag whether a low-priority exception should
|
500
|
-
override a high-prio one.
|
501
|
-
message (str): some text to add to the error. It is formatted
|
502
|
-
with the data when printed.
|
503
|
-
"""
|
504
|
-
rec = await self.get_error_record(subsystem, path)
|
505
|
-
if not force and hasattr(rec, "severity") and rec.severity < severity:
|
506
|
-
return rec
|
507
|
-
|
508
|
-
rec.severity = severity
|
509
|
-
rec.subsystem = subsystem
|
510
|
-
rec.path = path
|
511
|
-
rec.resolved = False
|
512
|
-
rec.count += 1
|
513
|
-
rec.last_seen = time()
|
514
|
-
if not hasattr(rec, "first_seen"):
|
515
|
-
rec.first_seen = rec.last_seen
|
516
|
-
|
517
|
-
try:
|
518
|
-
await rec.save()
|
519
|
-
except ClosedResourceError:
|
520
|
-
logger.exception(
|
521
|
-
"Could not save error %s %s: %s %r",
|
522
|
-
subsystem,
|
523
|
-
path,
|
524
|
-
message,
|
525
|
-
exc,
|
526
|
-
exc_info=exc,
|
527
|
-
)
|
528
|
-
return # owch, but can't be helped
|
529
|
-
|
530
|
-
r = await rec.real_entry.add_exc(
|
531
|
-
self.name,
|
532
|
-
exc=exc,
|
533
|
-
data=data,
|
534
|
-
comment=comment,
|
535
|
-
message=message,
|
536
|
-
)
|
537
|
-
return r
|
538
|
-
|
539
|
-
async def _pop(self, entry):
|
540
|
-
"""Override to deal with entry changes"""
|
541
|
-
if entry.subsystem is None or entry.path is None:
|
542
|
-
return
|
543
|
-
rec = await self.get_error_record(entry.subsystem, entry.path, create=False)
|
544
|
-
if rec is not entry:
|
545
|
-
return
|
546
|
-
|
547
|
-
try:
|
548
|
-
del (self._done if entry.resolved else self._active)[entry.subsystem][entry.path]
|
549
|
-
except KeyError:
|
550
|
-
pass
|
551
|
-
|
552
|
-
def _push(self, entry):
|
553
|
-
if entry.subsystem is None or entry.path is None:
|
554
|
-
return
|
555
|
-
|
556
|
-
if entry.resolved:
|
557
|
-
dest = self._done
|
558
|
-
self._latest.keep(entry)
|
559
|
-
else:
|
560
|
-
dest = self._active
|
561
|
-
dest[entry.subsystem][entry.path] = entry
|
build/lib/moat/kv/exceptions.py
DELETED
@@ -1,126 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
This module affords all MoaT-KV exceptions.
|
3
|
-
"""
|
4
|
-
|
5
|
-
# pylint: disable=unnecessary-pass
|
6
|
-
from __future__ import annotations
|
7
|
-
|
8
|
-
error_types = {}
|
9
|
-
|
10
|
-
|
11
|
-
def _typed(cls):
|
12
|
-
error_types[cls.etype] = cls
|
13
|
-
return cls
|
14
|
-
|
15
|
-
|
16
|
-
class MoaTKVError(RuntimeError):
|
17
|
-
"""Superclass of all MoaT-KV errors.
|
18
|
-
|
19
|
-
Abstract class.
|
20
|
-
"""
|
21
|
-
|
22
|
-
pass
|
23
|
-
|
24
|
-
|
25
|
-
class ServerError(MoaTKVError):
|
26
|
-
"""Generic server error.
|
27
|
-
|
28
|
-
This class includes errors forwarded to the client.
|
29
|
-
"""
|
30
|
-
|
31
|
-
pass
|
32
|
-
|
33
|
-
|
34
|
-
class ClientError(MoaTKVError):
|
35
|
-
"""Generic client error.
|
36
|
-
|
37
|
-
Abstract class.
|
38
|
-
"""
|
39
|
-
|
40
|
-
etype: str = None
|
41
|
-
|
42
|
-
|
43
|
-
@_typed
|
44
|
-
class ClientChainError(ClientError):
|
45
|
-
"""The chain you passed in didn't match the entry"""
|
46
|
-
|
47
|
-
etype = "chain"
|
48
|
-
|
49
|
-
|
50
|
-
@_typed
|
51
|
-
class ClientConnectionError(ClientError):
|
52
|
-
"""Some connection error"""
|
53
|
-
|
54
|
-
etype = "conn"
|
55
|
-
|
56
|
-
|
57
|
-
class ServerClosedError(ServerError):
|
58
|
-
"""The server closed our connection."""
|
59
|
-
|
60
|
-
pass
|
61
|
-
|
62
|
-
|
63
|
-
class ServerConnectionError(ServerError):
|
64
|
-
"""Some connection error"""
|
65
|
-
|
66
|
-
pass
|
67
|
-
|
68
|
-
|
69
|
-
class ACLError(ServerError):
|
70
|
-
"""An ACL did not match"""
|
71
|
-
|
72
|
-
pass
|
73
|
-
|
74
|
-
|
75
|
-
class CancelledError(ClientError):
|
76
|
-
"""A client call was cancelled."""
|
77
|
-
|
78
|
-
pass
|
79
|
-
|
80
|
-
|
81
|
-
class ClientAuthError(ClientError):
|
82
|
-
"""Authorization failed.
|
83
|
-
|
84
|
-
Abstract class.
|
85
|
-
"""
|
86
|
-
|
87
|
-
pass
|
88
|
-
|
89
|
-
|
90
|
-
class ClientAuthRequiredError(ClientAuthError):
|
91
|
-
"""Authorization required but missing."""
|
92
|
-
|
93
|
-
pass
|
94
|
-
|
95
|
-
|
96
|
-
class ClientAuthMethodError(ClientAuthError):
|
97
|
-
"""Wrong authorization method provided."""
|
98
|
-
|
99
|
-
pass
|
100
|
-
|
101
|
-
|
102
|
-
class MoaTKVauthError(ClientError):
|
103
|
-
"""Auth error.
|
104
|
-
|
105
|
-
Abstract class.
|
106
|
-
"""
|
107
|
-
|
108
|
-
pass
|
109
|
-
|
110
|
-
|
111
|
-
class NoAuthError(MoaTKVauthError):
|
112
|
-
"""Server-side error: auth required"""
|
113
|
-
|
114
|
-
pass
|
115
|
-
|
116
|
-
|
117
|
-
class NoAuthModuleError(MoaTKVauthError):
|
118
|
-
"""Server-side error: auth module doesn't exist"""
|
119
|
-
|
120
|
-
pass
|
121
|
-
|
122
|
-
|
123
|
-
class AuthFailedError(MoaTKVauthError):
|
124
|
-
"""Server-side error: auth failed"""
|
125
|
-
|
126
|
-
pass
|