ominfra 0.0.0.dev118__py3-none-any.whl → 0.0.0.dev120__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.
- ominfra/deploy/_executor.py +6 -2
- ominfra/deploy/poly/_main.py +6 -2
- ominfra/journald/fields.py +187 -0
- ominfra/journald/tailer.py +375 -312
- ominfra/pyremote/_runcommands.py +6 -2
- ominfra/scripts/journald2aws.py +381 -314
- ominfra/scripts/supervisor.py +697 -464
- ominfra/supervisor/__main__.py +1 -1
- ominfra/supervisor/context.py +50 -25
- ominfra/supervisor/dispatchers.py +12 -13
- ominfra/supervisor/events.py +4 -7
- ominfra/supervisor/main.py +68 -0
- ominfra/supervisor/poller.py +1 -3
- ominfra/supervisor/process.py +10 -11
- ominfra/supervisor/supervisor.py +161 -193
- {ominfra-0.0.0.dev118.dist-info → ominfra-0.0.0.dev120.dist-info}/METADATA +3 -3
- {ominfra-0.0.0.dev118.dist-info → ominfra-0.0.0.dev120.dist-info}/RECORD +21 -19
- {ominfra-0.0.0.dev118.dist-info → ominfra-0.0.0.dev120.dist-info}/LICENSE +0 -0
- {ominfra-0.0.0.dev118.dist-info → ominfra-0.0.0.dev120.dist-info}/WHEEL +0 -0
- {ominfra-0.0.0.dev118.dist-info → ominfra-0.0.0.dev120.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev118.dist-info → ominfra-0.0.0.dev120.dist-info}/top_level.txt +0 -0
ominfra/supervisor/__main__.py
CHANGED
ominfra/supervisor/context.py
CHANGED
@@ -2,7 +2,6 @@
|
|
2
2
|
import errno
|
3
3
|
import fcntl
|
4
4
|
import grp
|
5
|
-
import logging
|
6
5
|
import os
|
7
6
|
import pwd
|
8
7
|
import re
|
@@ -12,6 +11,8 @@ import stat
|
|
12
11
|
import typing as ta
|
13
12
|
import warnings
|
14
13
|
|
14
|
+
from omlish.lite.logs import log
|
15
|
+
|
15
16
|
from .compat import SignalReceiver
|
16
17
|
from .compat import close_fd
|
17
18
|
from .compat import mktempfile
|
@@ -23,6 +24,7 @@ from .datatypes import name_to_uid
|
|
23
24
|
from .exceptions import NoPermissionError
|
24
25
|
from .exceptions import NotExecutableError
|
25
26
|
from .exceptions import NotFoundError
|
27
|
+
from .poller import BasePoller
|
26
28
|
from .poller import Poller
|
27
29
|
from .states import SupervisorState
|
28
30
|
from .states import SupervisorStates
|
@@ -30,41 +32,47 @@ from .types import AbstractServerContext
|
|
30
32
|
from .types import AbstractSubprocess
|
31
33
|
|
32
34
|
|
33
|
-
log = logging.getLogger(__name__)
|
34
|
-
|
35
|
-
|
36
35
|
class ServerContext(AbstractServerContext):
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
36
|
+
def __init__(
|
37
|
+
self,
|
38
|
+
config: ServerConfig,
|
39
|
+
*,
|
40
|
+
epoch: int = 0,
|
41
|
+
) -> None:
|
43
42
|
super().__init__()
|
44
43
|
|
45
44
|
self._config = config
|
45
|
+
self._epoch = epoch
|
46
46
|
|
47
47
|
self._pid_history: ta.Dict[int, AbstractSubprocess] = {}
|
48
48
|
self._state: SupervisorState = SupervisorStates.RUNNING
|
49
49
|
|
50
|
-
self.
|
50
|
+
self._signal_receiver = SignalReceiver()
|
51
51
|
|
52
|
-
self.
|
52
|
+
self._poller: BasePoller = Poller()
|
53
53
|
|
54
|
-
if
|
55
|
-
uid = name_to_uid(
|
56
|
-
self.
|
57
|
-
self.
|
54
|
+
if config.user is not None:
|
55
|
+
uid = name_to_uid(config.user)
|
56
|
+
self._uid: ta.Optional[int] = uid
|
57
|
+
self._gid: ta.Optional[int] = gid_for_uid(uid)
|
58
58
|
else:
|
59
|
-
self.
|
60
|
-
self.
|
59
|
+
self._uid = None
|
60
|
+
self._gid = None
|
61
61
|
|
62
|
-
self.
|
62
|
+
self._unlink_pidfile = False
|
63
63
|
|
64
64
|
@property
|
65
65
|
def config(self) -> ServerConfig:
|
66
66
|
return self._config
|
67
67
|
|
68
|
+
@property
|
69
|
+
def epoch(self) -> int:
|
70
|
+
return self._epoch
|
71
|
+
|
72
|
+
@property
|
73
|
+
def first(self) -> bool:
|
74
|
+
return not self._epoch
|
75
|
+
|
68
76
|
@property
|
69
77
|
def state(self) -> SupervisorState:
|
70
78
|
return self._state
|
@@ -72,17 +80,26 @@ class ServerContext(AbstractServerContext):
|
|
72
80
|
def set_state(self, state: SupervisorState) -> None:
|
73
81
|
self._state = state
|
74
82
|
|
83
|
+
@property
|
84
|
+
def poller(self) -> BasePoller:
|
85
|
+
return self._poller
|
86
|
+
|
75
87
|
@property
|
76
88
|
def pid_history(self) -> ta.Dict[int, AbstractSubprocess]:
|
77
89
|
return self._pid_history
|
78
90
|
|
79
|
-
|
80
|
-
|
91
|
+
@property
|
92
|
+
def uid(self) -> ta.Optional[int]:
|
93
|
+
return self._uid
|
94
|
+
|
95
|
+
@property
|
96
|
+
def gid(self) -> ta.Optional[int]:
|
97
|
+
return self._gid
|
81
98
|
|
82
99
|
##
|
83
100
|
|
84
101
|
def set_signals(self) -> None:
|
85
|
-
self.
|
102
|
+
self._signal_receiver.install(
|
86
103
|
signal.SIGTERM,
|
87
104
|
signal.SIGINT,
|
88
105
|
signal.SIGQUIT,
|
@@ -193,7 +210,7 @@ class ServerContext(AbstractServerContext):
|
|
193
210
|
))
|
194
211
|
|
195
212
|
def cleanup(self) -> None:
|
196
|
-
if self.
|
213
|
+
if self._unlink_pidfile:
|
197
214
|
try_unlink(self.config.pidfile)
|
198
215
|
self.poller.close()
|
199
216
|
|
@@ -246,6 +263,7 @@ class ServerContext(AbstractServerContext):
|
|
246
263
|
# Parent
|
247
264
|
log.debug('supervisord forked; parent exiting')
|
248
265
|
real_exit(0)
|
266
|
+
|
249
267
|
# Child
|
250
268
|
log.info('daemonizing the supervisord process')
|
251
269
|
if self.config.directory:
|
@@ -255,11 +273,15 @@ class ServerContext(AbstractServerContext):
|
|
255
273
|
log.critical("can't chdir into %r: %s", self.config.directory, err)
|
256
274
|
else:
|
257
275
|
log.info('set current directory: %r', self.config.directory)
|
276
|
+
|
258
277
|
os.dup2(0, os.open('/dev/null', os.O_RDONLY))
|
259
278
|
os.dup2(1, os.open('/dev/null', os.O_WRONLY))
|
260
279
|
os.dup2(2, os.open('/dev/null', os.O_WRONLY))
|
280
|
+
|
261
281
|
os.setsid()
|
282
|
+
|
262
283
|
os.umask(self.config.umask)
|
284
|
+
|
263
285
|
# XXX Stevens, in his Advanced Unix book, section 13.3 (page 417) recommends calling umask(0) and closing unused
|
264
286
|
# file descriptors. In his Network Programming book, he additionally recommends ignoring SIGHUP and forking
|
265
287
|
# again after the setsid() call, for obscure SVR4 reasons.
|
@@ -274,7 +296,7 @@ class ServerContext(AbstractServerContext):
|
|
274
296
|
return logfile
|
275
297
|
|
276
298
|
def get_signal(self) -> ta.Optional[int]:
|
277
|
-
return self.
|
299
|
+
return self._signal_receiver.get_signal()
|
278
300
|
|
279
301
|
def write_pidfile(self) -> None:
|
280
302
|
pid = os.getpid()
|
@@ -284,7 +306,7 @@ class ServerContext(AbstractServerContext):
|
|
284
306
|
except OSError:
|
285
307
|
log.critical('could not write pidfile %s', self.config.pidfile)
|
286
308
|
else:
|
287
|
-
self.
|
309
|
+
self._unlink_pidfile = True
|
288
310
|
log.info('supervisord started with pid %s', pid)
|
289
311
|
|
290
312
|
|
@@ -337,11 +359,14 @@ def drop_privileges(user: ta.Union[int, str, None]) -> ta.Optional[str]:
|
|
337
359
|
os.setgroups(groups)
|
338
360
|
except OSError:
|
339
361
|
return 'Could not set groups of effective user'
|
362
|
+
|
340
363
|
try:
|
341
364
|
os.setgid(gid)
|
342
365
|
except OSError:
|
343
366
|
return 'Could not set group id of effective user'
|
367
|
+
|
344
368
|
os.setuid(uid)
|
369
|
+
|
345
370
|
return None
|
346
371
|
|
347
372
|
|
@@ -5,21 +5,20 @@ import logging
|
|
5
5
|
import os
|
6
6
|
import typing as ta
|
7
7
|
|
8
|
+
from omlish.lite.logs import log
|
9
|
+
|
8
10
|
from .compat import as_bytes
|
9
11
|
from .compat import compact_traceback
|
10
12
|
from .compat import find_prefix_at_end
|
11
13
|
from .compat import readfd
|
12
14
|
from .compat import strip_escapes
|
13
15
|
from .configs import ProcessConfig
|
16
|
+
from .events import EVENT_CALLBACKS
|
14
17
|
from .events import ProcessLogStderrEvent
|
15
18
|
from .events import ProcessLogStdoutEvent
|
16
|
-
from .events import notify_event
|
17
19
|
from .types import AbstractSubprocess
|
18
20
|
|
19
21
|
|
20
|
-
log = logging.getLogger(__name__)
|
21
|
-
|
22
|
-
|
23
22
|
class Dispatcher(abc.ABC):
|
24
23
|
|
25
24
|
def __init__(self, process: AbstractSubprocess, channel: str, fd: int) -> None:
|
@@ -176,16 +175,16 @@ class OutputDispatcher(Dispatcher):
|
|
176
175
|
# )
|
177
176
|
|
178
177
|
def remove_logs(self):
|
179
|
-
for
|
180
|
-
if
|
181
|
-
for handler in
|
178
|
+
for l in (self._normal_log, self._capture_log):
|
179
|
+
if l is not None:
|
180
|
+
for handler in l.handlers:
|
182
181
|
handler.remove() # type: ignore
|
183
182
|
handler.reopen() # type: ignore
|
184
183
|
|
185
184
|
def reopen_logs(self):
|
186
|
-
for
|
187
|
-
if
|
188
|
-
for handler in
|
185
|
+
for l in (self._normal_log, self._capture_log):
|
186
|
+
if l is not None:
|
187
|
+
for handler in l.handlers:
|
189
188
|
handler.reopen() # type: ignore
|
190
189
|
|
191
190
|
def _log(self, data):
|
@@ -208,10 +207,10 @@ class OutputDispatcher(Dispatcher):
|
|
208
207
|
|
209
208
|
if self._channel == 'stdout':
|
210
209
|
if self._stdout_events_enabled:
|
211
|
-
|
210
|
+
EVENT_CALLBACKS.notify(ProcessLogStdoutEvent(self._process, self._process.pid, data))
|
212
211
|
|
213
212
|
elif self._stderr_events_enabled:
|
214
|
-
|
213
|
+
EVENT_CALLBACKS.notify(ProcessLogStderrEvent(self._process, self._process.pid, data))
|
215
214
|
|
216
215
|
def record_output(self):
|
217
216
|
if self._capture_log is None:
|
@@ -262,7 +261,7 @@ class OutputDispatcher(Dispatcher):
|
|
262
261
|
channel = self._channel
|
263
262
|
procname = self._process.config.name
|
264
263
|
event = self.event_type(self._process, self._process.pid, data)
|
265
|
-
|
264
|
+
EVENT_CALLBACKS.notify(event)
|
266
265
|
|
267
266
|
log.debug('%r %s emitted a comm event', procname, channel)
|
268
267
|
for handler in self._capture_log.handlers:
|
ominfra/supervisor/events.py
CHANGED
@@ -29,12 +29,9 @@ class EventCallbacks:
|
|
29
29
|
|
30
30
|
EVENT_CALLBACKS = EventCallbacks()
|
31
31
|
|
32
|
-
notify_event = EVENT_CALLBACKS.notify
|
33
|
-
clear_events = EVENT_CALLBACKS.clear
|
34
|
-
|
35
32
|
|
36
33
|
class Event(abc.ABC): # noqa
|
37
|
-
"""Abstract event type
|
34
|
+
"""Abstract event type."""
|
38
35
|
|
39
36
|
|
40
37
|
class ProcessLogEvent(Event, abc.ABC):
|
@@ -114,7 +111,7 @@ class RemoteCommunicationEvent(Event):
|
|
114
111
|
|
115
112
|
|
116
113
|
class SupervisorStateChangeEvent(Event):
|
117
|
-
"""
|
114
|
+
"""Abstract class."""
|
118
115
|
|
119
116
|
def payload(self):
|
120
117
|
return ''
|
@@ -136,7 +133,7 @@ class EventRejectedEvent: # purposely does not subclass Event
|
|
136
133
|
|
137
134
|
|
138
135
|
class ProcessStateEvent(Event):
|
139
|
-
"""
|
136
|
+
"""Abstract class, never raised directly."""
|
140
137
|
frm = None
|
141
138
|
to = None
|
142
139
|
|
@@ -225,7 +222,7 @@ class ProcessGroupRemovedEvent(ProcessGroupEvent):
|
|
225
222
|
|
226
223
|
|
227
224
|
class TickEvent(Event):
|
228
|
-
"""
|
225
|
+
"""Abstract."""
|
229
226
|
|
230
227
|
def __init__(self, when, supervisord):
|
231
228
|
super().__init__()
|
@@ -0,0 +1,68 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
# ruff: noqa: UP006 UP007
|
3
|
+
# @omlish-amalg ../scripts/supervisor.py
|
4
|
+
import itertools
|
5
|
+
import json
|
6
|
+
import typing as ta
|
7
|
+
|
8
|
+
from omlish.lite.journald import journald_log_handler_factory
|
9
|
+
from omlish.lite.logs import configure_standard_logging
|
10
|
+
from omlish.lite.marshal import unmarshal_obj
|
11
|
+
|
12
|
+
from .compat import ExitNow
|
13
|
+
from .configs import ServerConfig
|
14
|
+
from .context import ServerContext
|
15
|
+
from .states import SupervisorStates
|
16
|
+
from .supervisor import Supervisor
|
17
|
+
|
18
|
+
|
19
|
+
def main(
|
20
|
+
argv: ta.Optional[ta.Sequence[str]] = None,
|
21
|
+
*,
|
22
|
+
no_logging: bool = False,
|
23
|
+
) -> None:
|
24
|
+
import argparse
|
25
|
+
|
26
|
+
parser = argparse.ArgumentParser()
|
27
|
+
parser.add_argument('config_file', metavar='config-file')
|
28
|
+
parser.add_argument('--no-journald', action='store_true')
|
29
|
+
args = parser.parse_args(argv)
|
30
|
+
|
31
|
+
#
|
32
|
+
|
33
|
+
if not (cf := args.config_file):
|
34
|
+
raise RuntimeError('No config file specified')
|
35
|
+
|
36
|
+
if not no_logging:
|
37
|
+
configure_standard_logging(
|
38
|
+
'INFO',
|
39
|
+
handler_factory=journald_log_handler_factory if not args.no_journald else None,
|
40
|
+
)
|
41
|
+
|
42
|
+
#
|
43
|
+
|
44
|
+
# if we hup, restart by making a new Supervisor()
|
45
|
+
for epoch in itertools.count():
|
46
|
+
with open(cf) as f:
|
47
|
+
config_src = f.read()
|
48
|
+
|
49
|
+
config_dct = json.loads(config_src)
|
50
|
+
config: ServerConfig = unmarshal_obj(config_dct, ServerConfig)
|
51
|
+
|
52
|
+
context = ServerContext(
|
53
|
+
config,
|
54
|
+
epoch=epoch,
|
55
|
+
)
|
56
|
+
|
57
|
+
supervisor = Supervisor(context)
|
58
|
+
try:
|
59
|
+
supervisor.main()
|
60
|
+
except ExitNow:
|
61
|
+
pass
|
62
|
+
|
63
|
+
if context.state < SupervisorStates.RESTARTING:
|
64
|
+
break
|
65
|
+
|
66
|
+
|
67
|
+
if __name__ == '__main__':
|
68
|
+
main()
|
ominfra/supervisor/poller.py
CHANGED
ominfra/supervisor/process.py
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
# ruff: noqa: UP006 UP007
|
2
2
|
import errno
|
3
3
|
import functools
|
4
|
-
import
|
5
|
-
import os
|
4
|
+
import os.path
|
6
5
|
import shlex
|
7
6
|
import signal
|
8
7
|
import time
|
9
8
|
import traceback
|
10
9
|
import typing as ta
|
11
10
|
|
11
|
+
from omlish.lite.logs import log
|
12
|
+
|
12
13
|
from .compat import as_bytes
|
13
14
|
from .compat import as_string
|
14
15
|
from .compat import close_fd
|
@@ -29,6 +30,7 @@ from .datatypes import RestartUnconditionally
|
|
29
30
|
from .dispatchers import Dispatcher
|
30
31
|
from .dispatchers import InputDispatcher
|
31
32
|
from .dispatchers import OutputDispatcher
|
33
|
+
from .events import EVENT_CALLBACKS
|
32
34
|
from .events import EventRejectedEvent
|
33
35
|
from .events import ProcessCommunicationEvent
|
34
36
|
from .events import ProcessCommunicationStderrEvent
|
@@ -42,7 +44,6 @@ from .events import ProcessStateStartingEvent
|
|
42
44
|
from .events import ProcessStateStoppedEvent
|
43
45
|
from .events import ProcessStateStoppingEvent
|
44
46
|
from .events import ProcessStateUnknownEvent
|
45
|
-
from .events import notify_event
|
46
47
|
from .exceptions import BadCommandError
|
47
48
|
from .exceptions import ProcessError
|
48
49
|
from .states import STOPPED_STATES
|
@@ -54,9 +55,6 @@ from .types import AbstractServerContext
|
|
54
55
|
from .types import AbstractSubprocess
|
55
56
|
|
56
57
|
|
57
|
-
log = logging.getLogger(__name__)
|
58
|
-
|
59
|
-
|
60
58
|
@functools.total_ordering
|
61
59
|
class Subprocess(AbstractSubprocess):
|
62
60
|
"""A class to manage a subprocess."""
|
@@ -209,7 +207,7 @@ class Subprocess(AbstractSubprocess):
|
|
209
207
|
event_class = self.event_map.get(new_state)
|
210
208
|
if event_class is not None:
|
211
209
|
event = event_class(self, old_state, expected)
|
212
|
-
|
210
|
+
EVENT_CALLBACKS.notify(event)
|
213
211
|
|
214
212
|
return True
|
215
213
|
|
@@ -326,6 +324,7 @@ class Subprocess(AbstractSubprocess):
|
|
326
324
|
os.dup2(self._pipes['child_stdout'], 2)
|
327
325
|
else:
|
328
326
|
os.dup2(self._pipes['child_stderr'], 2)
|
327
|
+
# FIXME: leave debugger fds
|
329
328
|
for i in range(3, self.context.config.minfds):
|
330
329
|
close_fd(i)
|
331
330
|
|
@@ -361,7 +360,7 @@ class Subprocess(AbstractSubprocess):
|
|
361
360
|
cwd = self.config.directory
|
362
361
|
try:
|
363
362
|
if cwd is not None:
|
364
|
-
os.chdir(cwd)
|
363
|
+
os.chdir(os.path.expanduser(cwd))
|
365
364
|
except OSError as why:
|
366
365
|
code = errno.errorcode.get(why.args[0], why.args[0])
|
367
366
|
msg = f"couldn't chdir to {cwd}: {code}\n"
|
@@ -417,7 +416,7 @@ class Subprocess(AbstractSubprocess):
|
|
417
416
|
return self.kill(self.config.stopsignal)
|
418
417
|
|
419
418
|
def stop_report(self) -> None:
|
420
|
-
"""
|
419
|
+
"""Log a 'waiting for x to stop' message with throttling."""
|
421
420
|
if self.state == ProcessStates.STOPPING:
|
422
421
|
now = time.time()
|
423
422
|
|
@@ -547,7 +546,7 @@ class Subprocess(AbstractSubprocess):
|
|
547
546
|
return None
|
548
547
|
|
549
548
|
def finish(self, sts: int) -> None:
|
550
|
-
"""
|
549
|
+
"""The process was reaped and we need to report and manage its state."""
|
551
550
|
|
552
551
|
self.drain()
|
553
552
|
|
@@ -628,7 +627,7 @@ class Subprocess(AbstractSubprocess):
|
|
628
627
|
# system that this event was rejected so it can be processed again.
|
629
628
|
if self.event is not None:
|
630
629
|
# Note: this should only be true if we were in the BUSY state when finish() was called.
|
631
|
-
|
630
|
+
EVENT_CALLBACKS.notify(EventRejectedEvent(self, self.event)) # type: ignore
|
632
631
|
self.event = None
|
633
632
|
|
634
633
|
def set_uid(self) -> ta.Optional[str]:
|