ominfra 0.0.0.dev131__py3-none-any.whl → 0.0.0.dev132__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- ominfra/scripts/journald2aws.py +8 -0
- ominfra/scripts/supervisor.py +172 -66
- ominfra/supervisor/dispatchersimpl.py +4 -3
- ominfra/supervisor/events.py +5 -2
- ominfra/supervisor/http.py +7 -0
- ominfra/supervisor/inject.py +8 -2
- ominfra/supervisor/io.py +2 -2
- ominfra/supervisor/main.py +13 -10
- ominfra/supervisor/processimpl.py +0 -1
- ominfra/supervisor/states.py +11 -0
- ominfra/supervisor/supervisor.py +26 -26
- ominfra/supervisor/types.py +2 -1
- {ominfra-0.0.0.dev131.dist-info → ominfra-0.0.0.dev132.dist-info}/METADATA +3 -3
- {ominfra-0.0.0.dev131.dist-info → ominfra-0.0.0.dev132.dist-info}/RECORD +18 -18
- {ominfra-0.0.0.dev131.dist-info → ominfra-0.0.0.dev132.dist-info}/LICENSE +0 -0
- {ominfra-0.0.0.dev131.dist-info → ominfra-0.0.0.dev132.dist-info}/WHEEL +0 -0
- {ominfra-0.0.0.dev131.dist-info → ominfra-0.0.0.dev132.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev131.dist-info → ominfra-0.0.0.dev132.dist-info}/top_level.txt +0 -0
ominfra/scripts/journald2aws.py
CHANGED
@@ -1627,6 +1627,14 @@ class ExitStacked:
|
|
1627
1627
|
##
|
1628
1628
|
|
1629
1629
|
|
1630
|
+
@contextlib.contextmanager
|
1631
|
+
def defer(fn: ta.Callable) -> ta.Generator[ta.Callable, None, None]:
|
1632
|
+
try:
|
1633
|
+
yield fn
|
1634
|
+
finally:
|
1635
|
+
fn()
|
1636
|
+
|
1637
|
+
|
1630
1638
|
@contextlib.contextmanager
|
1631
1639
|
def attr_setting(obj, attr, val, *, default=None): # noqa
|
1632
1640
|
not_set = object()
|
ominfra/scripts/supervisor.py
CHANGED
@@ -116,6 +116,10 @@ A2 = ta.TypeVar('A2')
|
|
116
116
|
|
117
117
|
# events.py
|
118
118
|
EventCallback = ta.Callable[['Event'], None]
|
119
|
+
ProcessOutputChannel = ta.Literal['stdout', 'stderr'] # ta.TypeAlias
|
120
|
+
|
121
|
+
# ../../omlish/lite/contextmanagers.py
|
122
|
+
ExitStackedT = ta.TypeVar('ExitStackedT', bound='ExitStacked')
|
119
123
|
|
120
124
|
# ../../omlish/lite/http/parsing.py
|
121
125
|
HttpHeaders = http.client.HTTPMessage # ta.TypeAlias
|
@@ -1077,6 +1081,17 @@ class ProcessState(enum.IntEnum):
|
|
1077
1081
|
return self in SIGNALABLE_STATES
|
1078
1082
|
|
1079
1083
|
|
1084
|
+
# http://supervisord.org/subprocess.html
|
1085
|
+
STATE_TRANSITIONS = {
|
1086
|
+
ProcessState.STOPPED: (ProcessState.STARTING,),
|
1087
|
+
ProcessState.STARTING: (ProcessState.RUNNING, ProcessState.BACKOFF, ProcessState.STOPPING),
|
1088
|
+
ProcessState.RUNNING: (ProcessState.STOPPING, ProcessState.EXITED),
|
1089
|
+
ProcessState.BACKOFF: (ProcessState.STARTING, ProcessState.FATAL),
|
1090
|
+
ProcessState.STOPPING: (ProcessState.STOPPED,),
|
1091
|
+
ProcessState.EXITED: (ProcessState.STARTING,),
|
1092
|
+
ProcessState.FATAL: (ProcessState.STARTING,),
|
1093
|
+
}
|
1094
|
+
|
1080
1095
|
STOPPED_STATES = (
|
1081
1096
|
ProcessState.STOPPED,
|
1082
1097
|
ProcessState.EXITED,
|
@@ -1576,16 +1591,16 @@ class FdIoPoller(abc.ABC):
|
|
1576
1591
|
def register_readable(self, fd: int) -> bool:
|
1577
1592
|
if fd in self._readable:
|
1578
1593
|
return False
|
1579
|
-
self._readable.add(fd)
|
1580
1594
|
self._register_readable(fd)
|
1595
|
+
self._readable.add(fd)
|
1581
1596
|
return True
|
1582
1597
|
|
1583
1598
|
@ta.final
|
1584
1599
|
def register_writable(self, fd: int) -> bool:
|
1585
1600
|
if fd in self._writable:
|
1586
1601
|
return False
|
1587
|
-
self._writable.add(fd)
|
1588
1602
|
self._register_writable(fd)
|
1603
|
+
self._writable.add(fd)
|
1589
1604
|
return True
|
1590
1605
|
|
1591
1606
|
@ta.final
|
@@ -1689,24 +1704,24 @@ if hasattr(select, 'poll'):
|
|
1689
1704
|
|
1690
1705
|
#
|
1691
1706
|
|
1692
|
-
_READ = select.POLLIN | select.POLLPRI | select.POLLHUP
|
1693
|
-
_WRITE = select.POLLOUT
|
1694
|
-
|
1695
1707
|
def _register_readable(self, fd: int) -> None:
|
1696
|
-
self._update_registration(fd)
|
1708
|
+
self._update_registration(fd, r=True, w=fd in self._writable)
|
1697
1709
|
|
1698
1710
|
def _register_writable(self, fd: int) -> None:
|
1699
|
-
self._update_registration(fd)
|
1711
|
+
self._update_registration(fd, r=fd in self._readable, w=True)
|
1700
1712
|
|
1701
1713
|
def _unregister_readable(self, fd: int) -> None:
|
1702
|
-
self._update_registration(fd)
|
1714
|
+
self._update_registration(fd, r=False, w=False)
|
1703
1715
|
|
1704
1716
|
def _unregister_writable(self, fd: int) -> None:
|
1705
|
-
self._update_registration(fd)
|
1717
|
+
self._update_registration(fd, r=fd in self._readable, w=False)
|
1706
1718
|
|
1707
|
-
|
1708
|
-
|
1709
|
-
|
1719
|
+
#
|
1720
|
+
|
1721
|
+
_READ = select.POLLIN | select.POLLPRI | select.POLLHUP
|
1722
|
+
_WRITE = select.POLLOUT
|
1723
|
+
|
1724
|
+
def _update_registration(self, fd: int, *, r: bool, w: bool) -> None:
|
1710
1725
|
if r or w:
|
1711
1726
|
self._poller.register(fd, (self._READ if r else 0) | (self._WRITE if w else 0))
|
1712
1727
|
else:
|
@@ -2119,7 +2134,7 @@ class EventCallbacks:
|
|
2119
2134
|
|
2120
2135
|
|
2121
2136
|
class ProcessLogEvent(Event, abc.ABC):
|
2122
|
-
channel: ta.
|
2137
|
+
channel: ta.ClassVar[ProcessOutputChannel]
|
2123
2138
|
|
2124
2139
|
def __init__(self, process, pid, data):
|
2125
2140
|
super().__init__()
|
@@ -2144,7 +2159,7 @@ class ProcessCommunicationEvent(Event, abc.ABC):
|
|
2144
2159
|
BEGIN_TOKEN = b'<!--XSUPERVISOR:BEGIN-->'
|
2145
2160
|
END_TOKEN = b'<!--XSUPERVISOR:END-->'
|
2146
2161
|
|
2147
|
-
channel: ta.ClassVar[
|
2162
|
+
channel: ta.ClassVar[ProcessOutputChannel]
|
2148
2163
|
|
2149
2164
|
def __init__(self, process, pid, data):
|
2150
2165
|
super().__init__()
|
@@ -2516,6 +2531,64 @@ def get_user(name: str) -> User:
|
|
2516
2531
|
)
|
2517
2532
|
|
2518
2533
|
|
2534
|
+
########################################
|
2535
|
+
# ../../../omlish/lite/contextmanagers.py
|
2536
|
+
|
2537
|
+
|
2538
|
+
##
|
2539
|
+
|
2540
|
+
|
2541
|
+
class ExitStacked:
|
2542
|
+
_exit_stack: ta.Optional[contextlib.ExitStack] = None
|
2543
|
+
|
2544
|
+
def __enter__(self: ExitStackedT) -> ExitStackedT:
|
2545
|
+
check_state(self._exit_stack is None)
|
2546
|
+
es = self._exit_stack = contextlib.ExitStack()
|
2547
|
+
es.__enter__()
|
2548
|
+
return self
|
2549
|
+
|
2550
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
2551
|
+
if (es := self._exit_stack) is None:
|
2552
|
+
return None
|
2553
|
+
self._exit_contexts()
|
2554
|
+
return es.__exit__(exc_type, exc_val, exc_tb)
|
2555
|
+
|
2556
|
+
def _exit_contexts(self) -> None:
|
2557
|
+
pass
|
2558
|
+
|
2559
|
+
def _enter_context(self, cm: ta.ContextManager[T]) -> T:
|
2560
|
+
es = check_not_none(self._exit_stack)
|
2561
|
+
return es.enter_context(cm)
|
2562
|
+
|
2563
|
+
|
2564
|
+
##
|
2565
|
+
|
2566
|
+
|
2567
|
+
@contextlib.contextmanager
|
2568
|
+
def defer(fn: ta.Callable) -> ta.Generator[ta.Callable, None, None]:
|
2569
|
+
try:
|
2570
|
+
yield fn
|
2571
|
+
finally:
|
2572
|
+
fn()
|
2573
|
+
|
2574
|
+
|
2575
|
+
@contextlib.contextmanager
|
2576
|
+
def attr_setting(obj, attr, val, *, default=None): # noqa
|
2577
|
+
not_set = object()
|
2578
|
+
orig = getattr(obj, attr, not_set)
|
2579
|
+
try:
|
2580
|
+
setattr(obj, attr, val)
|
2581
|
+
if orig is not not_set:
|
2582
|
+
yield orig
|
2583
|
+
else:
|
2584
|
+
yield default
|
2585
|
+
finally:
|
2586
|
+
if orig is not_set:
|
2587
|
+
delattr(obj, attr)
|
2588
|
+
else:
|
2589
|
+
setattr(obj, attr, orig)
|
2590
|
+
|
2591
|
+
|
2519
2592
|
########################################
|
2520
2593
|
# ../../../omlish/lite/fdio/handlers.py
|
2521
2594
|
|
@@ -2624,19 +2697,40 @@ if sys.platform == 'darwin' or sys.platform.startswith('freebsd'):
|
|
2624
2697
|
#
|
2625
2698
|
|
2626
2699
|
def _register_readable(self, fd: int) -> None:
|
2627
|
-
self.
|
2700
|
+
self._update_registration(fd, 'read', 'add')
|
2628
2701
|
|
2629
2702
|
def _register_writable(self, fd: int) -> None:
|
2630
|
-
self.
|
2703
|
+
self._update_registration(fd, 'write', 'add')
|
2631
2704
|
|
2632
2705
|
def _unregister_readable(self, fd: int) -> None:
|
2633
|
-
self.
|
2706
|
+
self._update_registration(fd, 'read', 'del')
|
2634
2707
|
|
2635
2708
|
def _unregister_writable(self, fd: int) -> None:
|
2636
|
-
self.
|
2709
|
+
self._update_registration(fd, 'write', 'del')
|
2710
|
+
|
2711
|
+
#
|
2637
2712
|
|
2638
|
-
|
2639
|
-
|
2713
|
+
_CONTROL_FILTER_BY_READ_OR_WRITE: ta.ClassVar[ta.Mapping[ta.Literal['read', 'write'], int]] = {
|
2714
|
+
'read': select.KQ_FILTER_READ,
|
2715
|
+
'write': select.KQ_FILTER_WRITE,
|
2716
|
+
}
|
2717
|
+
|
2718
|
+
_CONTROL_FLAGS_BY_ADD_OR_DEL: ta.ClassVar[ta.Mapping[ta.Literal['add', 'del'], int]] = {
|
2719
|
+
'add': select.KQ_EV_ADD,
|
2720
|
+
'del': select.KQ_EV_DELETE,
|
2721
|
+
}
|
2722
|
+
|
2723
|
+
def _update_registration(
|
2724
|
+
self,
|
2725
|
+
fd: int,
|
2726
|
+
read_or_write: ta.Literal['read', 'write'],
|
2727
|
+
add_or_del: ta.Literal['add', 'del'],
|
2728
|
+
) -> None: # noqa
|
2729
|
+
ke = select.kevent(
|
2730
|
+
fd,
|
2731
|
+
filter=self._CONTROL_FILTER_BY_READ_OR_WRITE[read_or_write],
|
2732
|
+
flags=self._CONTROL_FLAGS_BY_ADD_OR_DEL[add_or_del],
|
2733
|
+
)
|
2640
2734
|
kq = self._get_kqueue()
|
2641
2735
|
try:
|
2642
2736
|
kq.control([ke], 0)
|
@@ -2647,7 +2741,8 @@ if sys.platform == 'darwin' or sys.platform.startswith('freebsd'):
|
|
2647
2741
|
pass
|
2648
2742
|
elif exc.errno == errno.ENOENT:
|
2649
2743
|
# Can happen when trying to remove an already closed socket
|
2650
|
-
|
2744
|
+
if add_or_del == 'add':
|
2745
|
+
raise
|
2651
2746
|
else:
|
2652
2747
|
raise
|
2653
2748
|
|
@@ -5951,7 +6046,7 @@ class HasDispatchers(abc.ABC):
|
|
5951
6046
|
class ProcessDispatcher(FdIoHandler, abc.ABC):
|
5952
6047
|
@property
|
5953
6048
|
@abc.abstractmethod
|
5954
|
-
def channel(self) ->
|
6049
|
+
def channel(self) -> ProcessOutputChannel:
|
5955
6050
|
raise NotImplementedError
|
5956
6051
|
|
5957
6052
|
@property
|
@@ -6240,7 +6335,7 @@ class BaseProcessDispatcherImpl(ProcessDispatcher, abc.ABC):
|
|
6240
6335
|
def __init__(
|
6241
6336
|
self,
|
6242
6337
|
process: Process,
|
6243
|
-
channel:
|
6338
|
+
channel: ProcessOutputChannel,
|
6244
6339
|
fd: Fd,
|
6245
6340
|
*,
|
6246
6341
|
event_callbacks: EventCallbacks,
|
@@ -6268,7 +6363,7 @@ class BaseProcessDispatcherImpl(ProcessDispatcher, abc.ABC):
|
|
6268
6363
|
return self._process
|
6269
6364
|
|
6270
6365
|
@property
|
6271
|
-
def channel(self) ->
|
6366
|
+
def channel(self) -> ProcessOutputChannel:
|
6272
6367
|
return self._channel
|
6273
6368
|
|
6274
6369
|
def fd(self) -> Fd:
|
@@ -6515,7 +6610,7 @@ class ProcessInputDispatcherImpl(BaseProcessDispatcherImpl, ProcessInputDispatch
|
|
6515
6610
|
def __init__(
|
6516
6611
|
self,
|
6517
6612
|
process: Process,
|
6518
|
-
channel:
|
6613
|
+
channel: ProcessOutputChannel,
|
6519
6614
|
fd: Fd,
|
6520
6615
|
*,
|
6521
6616
|
event_callbacks: EventCallbacks,
|
@@ -7024,9 +7119,9 @@ class IoManager(HasDispatchers):
|
|
7024
7119
|
)
|
7025
7120
|
|
7026
7121
|
timeout = 1 # this cannot be fewer than the smallest TickEvent (5)
|
7027
|
-
|
7122
|
+
|
7028
7123
|
polled = self._poller.poll(timeout)
|
7029
|
-
|
7124
|
+
|
7030
7125
|
if polled.msg is not None:
|
7031
7126
|
log.error(polled.msg)
|
7032
7127
|
if polled.exc is not None:
|
@@ -7151,6 +7246,8 @@ class HttpServer(HasDispatchers):
|
|
7151
7246
|
self,
|
7152
7247
|
handler: Handler,
|
7153
7248
|
addr: Address = Address(('localhost', 8000)),
|
7249
|
+
*,
|
7250
|
+
exit_stack: contextlib.ExitStack,
|
7154
7251
|
) -> None:
|
7155
7252
|
super().__init__()
|
7156
7253
|
|
@@ -7161,6 +7258,8 @@ class HttpServer(HasDispatchers):
|
|
7161
7258
|
|
7162
7259
|
self._conns: ta.List[CoroHttpServerConnectionFdIoHandler] = []
|
7163
7260
|
|
7261
|
+
exit_stack.enter_context(defer(self._server.close)) # noqa
|
7262
|
+
|
7164
7263
|
def get_dispatchers(self) -> Dispatchers:
|
7165
7264
|
l = []
|
7166
7265
|
for c in self._conns:
|
@@ -7205,6 +7304,7 @@ class SupervisorHttpHandler:
|
|
7205
7304
|
'processes': {
|
7206
7305
|
p.name: {
|
7207
7306
|
'pid': p.pid,
|
7307
|
+
'state': p.state.name,
|
7208
7308
|
}
|
7209
7309
|
for p in g
|
7210
7310
|
},
|
@@ -7572,7 +7672,6 @@ class ProcessImpl(Process):
|
|
7572
7672
|
self._last_stop = now
|
7573
7673
|
|
7574
7674
|
if now > self._last_start:
|
7575
|
-
log.info(f'{now - self._last_start=}') # noqa
|
7576
7675
|
too_quickly = now - self._last_start < self._config.start_secs
|
7577
7676
|
else:
|
7578
7677
|
too_quickly = False
|
@@ -8227,11 +8326,29 @@ class Supervisor:
|
|
8227
8326
|
#
|
8228
8327
|
|
8229
8328
|
def _run_once(self) -> None:
|
8230
|
-
|
8231
|
-
|
8232
|
-
|
8329
|
+
if self._states.state < SupervisorState.RUNNING:
|
8330
|
+
if not self._stopping:
|
8331
|
+
# first time, set the stopping flag, do a notification and set stop_groups
|
8332
|
+
self._stopping = True
|
8333
|
+
self._stop_groups = sorted(self._process_groups)
|
8334
|
+
self._event_callbacks.notify(SupervisorStoppingEvent())
|
8335
|
+
|
8336
|
+
self._ordered_stop_groups_phase_1()
|
8337
|
+
|
8338
|
+
if not self.shutdown_report():
|
8339
|
+
# if there are no unstopped processes (we're done killing everything), it's OK to shutdown or reload
|
8340
|
+
raise ExitNow
|
8341
|
+
|
8342
|
+
self._io.poll()
|
8343
|
+
|
8344
|
+
for group in sorted(self._process_groups):
|
8345
|
+
for process in group:
|
8346
|
+
process.transition()
|
8347
|
+
|
8233
8348
|
self._reap()
|
8349
|
+
|
8234
8350
|
self._signal_handler.handle_signals()
|
8351
|
+
|
8235
8352
|
self._tick()
|
8236
8353
|
|
8237
8354
|
if self._states.state < SupervisorState.RUNNING:
|
@@ -8253,38 +8370,18 @@ class Supervisor:
|
|
8253
8370
|
# down, so push it back on to the end of the stop group queue
|
8254
8371
|
self._stop_groups.append(group)
|
8255
8372
|
|
8256
|
-
|
8257
|
-
sorted_groups = list(self._process_groups)
|
8258
|
-
sorted_groups.sort()
|
8259
|
-
|
8260
|
-
if self._states.state < SupervisorState.RUNNING:
|
8261
|
-
if not self._stopping:
|
8262
|
-
# first time, set the stopping flag, do a notification and set stop_groups
|
8263
|
-
self._stopping = True
|
8264
|
-
self._stop_groups = sorted_groups[:]
|
8265
|
-
self._event_callbacks.notify(SupervisorStoppingEvent())
|
8266
|
-
|
8267
|
-
self._ordered_stop_groups_phase_1()
|
8268
|
-
|
8269
|
-
if not self.shutdown_report():
|
8270
|
-
# if there are no unstopped processes (we're done killing everything), it's OK to shutdown or reload
|
8271
|
-
raise ExitNow
|
8272
|
-
|
8273
|
-
self._io.poll()
|
8274
|
-
|
8275
|
-
for group in sorted_groups:
|
8276
|
-
for process in group:
|
8277
|
-
process.transition()
|
8373
|
+
#
|
8278
8374
|
|
8279
8375
|
def _reap(self, *, once: bool = False, depth: int = 0) -> None:
|
8280
8376
|
if depth >= 100:
|
8281
8377
|
return
|
8282
8378
|
|
8283
8379
|
wp = waitpid()
|
8284
|
-
|
8380
|
+
|
8285
8381
|
if wp is None or not wp.pid:
|
8286
8382
|
return
|
8287
8383
|
|
8384
|
+
log.info(f'Waited pid: {wp}') # noqa
|
8288
8385
|
process = self._pid_history.get(wp.pid, None)
|
8289
8386
|
if process is None:
|
8290
8387
|
_, msg = decode_wait_status(wp.sts)
|
@@ -8297,6 +8394,8 @@ class Supervisor:
|
|
8297
8394
|
# keep reaping until no more kids to reap, but don't recurse infinitely
|
8298
8395
|
self._reap(once=False, depth=depth + 1)
|
8299
8396
|
|
8397
|
+
#
|
8398
|
+
|
8300
8399
|
def _tick(self, now: ta.Optional[float] = None) -> None:
|
8301
8400
|
"""Send one or more 'tick' events when the timeslice related to the period for the event type rolls over"""
|
8302
8401
|
|
@@ -8362,6 +8461,7 @@ class _FdIoPollerDaemonizeListener(DaemonizeListener):
|
|
8362
8461
|
|
8363
8462
|
|
8364
8463
|
def bind_server(
|
8464
|
+
exit_stack: contextlib.ExitStack,
|
8365
8465
|
config: ServerConfig,
|
8366
8466
|
*,
|
8367
8467
|
server_epoch: ta.Optional[ServerEpoch] = None,
|
@@ -8370,6 +8470,8 @@ def bind_server(
|
|
8370
8470
|
lst: ta.List[InjectorBindingOrBindings] = [
|
8371
8471
|
inj.bind(config),
|
8372
8472
|
|
8473
|
+
inj.bind(exit_stack),
|
8474
|
+
|
8373
8475
|
inj.bind_array(DaemonizeListener),
|
8374
8476
|
inj.bind_array_type(DaemonizeListener, DaemonizeListeners),
|
8375
8477
|
|
@@ -8425,8 +8527,10 @@ def bind_server(
|
|
8425
8527
|
PollFdIoPoller,
|
8426
8528
|
SelectFdIoPoller,
|
8427
8529
|
]))
|
8428
|
-
lst.
|
8429
|
-
|
8530
|
+
lst.extend([
|
8531
|
+
inj.bind(poller_impl, key=FdIoPoller, singleton=True),
|
8532
|
+
inj.bind(_FdIoPollerDaemonizeListener, array=True, singleton=True),
|
8533
|
+
])
|
8430
8534
|
|
8431
8535
|
#
|
8432
8536
|
|
@@ -8495,18 +8599,20 @@ def main(
|
|
8495
8599
|
prepare=prepare_server_config,
|
8496
8600
|
)
|
8497
8601
|
|
8498
|
-
|
8499
|
-
|
8500
|
-
|
8501
|
-
|
8502
|
-
|
8602
|
+
with contextlib.ExitStack() as es:
|
8603
|
+
injector = inj.create_injector(bind_server(
|
8604
|
+
es,
|
8605
|
+
config,
|
8606
|
+
server_epoch=ServerEpoch(epoch),
|
8607
|
+
inherited_fds=inherited_fds,
|
8608
|
+
))
|
8503
8609
|
|
8504
|
-
|
8610
|
+
supervisor = injector[Supervisor]
|
8505
8611
|
|
8506
|
-
|
8507
|
-
|
8508
|
-
|
8509
|
-
|
8612
|
+
try:
|
8613
|
+
supervisor.main()
|
8614
|
+
except ExitNow:
|
8615
|
+
pass
|
8510
8616
|
|
8511
8617
|
if supervisor.state < SupervisorState.RESTARTING:
|
8512
8618
|
break
|
@@ -13,6 +13,7 @@ from .events import EventCallbacks
|
|
13
13
|
from .events import ProcessCommunicationEvent
|
14
14
|
from .events import ProcessLogStderrEvent
|
15
15
|
from .events import ProcessLogStdoutEvent
|
16
|
+
from .events import ProcessOutputChannel
|
16
17
|
from .types import Process
|
17
18
|
from .types import ProcessDispatcher
|
18
19
|
from .types import ProcessInputDispatcher
|
@@ -29,7 +30,7 @@ class BaseProcessDispatcherImpl(ProcessDispatcher, abc.ABC):
|
|
29
30
|
def __init__(
|
30
31
|
self,
|
31
32
|
process: Process,
|
32
|
-
channel:
|
33
|
+
channel: ProcessOutputChannel,
|
33
34
|
fd: Fd,
|
34
35
|
*,
|
35
36
|
event_callbacks: EventCallbacks,
|
@@ -57,7 +58,7 @@ class BaseProcessDispatcherImpl(ProcessDispatcher, abc.ABC):
|
|
57
58
|
return self._process
|
58
59
|
|
59
60
|
@property
|
60
|
-
def channel(self) ->
|
61
|
+
def channel(self) -> ProcessOutputChannel:
|
61
62
|
return self._channel
|
62
63
|
|
63
64
|
def fd(self) -> Fd:
|
@@ -304,7 +305,7 @@ class ProcessInputDispatcherImpl(BaseProcessDispatcherImpl, ProcessInputDispatch
|
|
304
305
|
def __init__(
|
305
306
|
self,
|
306
307
|
process: Process,
|
307
|
-
channel:
|
308
|
+
channel: ProcessOutputChannel,
|
308
309
|
fd: Fd,
|
309
310
|
*,
|
310
311
|
event_callbacks: EventCallbacks,
|
ominfra/supervisor/events.py
CHANGED
@@ -8,6 +8,9 @@ from .states import ProcessState
|
|
8
8
|
EventCallback = ta.Callable[['Event'], None]
|
9
9
|
|
10
10
|
|
11
|
+
ProcessOutputChannel = ta.Literal['stdout', 'stderr'] # ta.TypeAlias
|
12
|
+
|
13
|
+
|
11
14
|
##
|
12
15
|
|
13
16
|
|
@@ -43,7 +46,7 @@ class EventCallbacks:
|
|
43
46
|
|
44
47
|
|
45
48
|
class ProcessLogEvent(Event, abc.ABC):
|
46
|
-
channel: ta.
|
49
|
+
channel: ta.ClassVar[ProcessOutputChannel]
|
47
50
|
|
48
51
|
def __init__(self, process, pid, data):
|
49
52
|
super().__init__()
|
@@ -68,7 +71,7 @@ class ProcessCommunicationEvent(Event, abc.ABC):
|
|
68
71
|
BEGIN_TOKEN = b'<!--XSUPERVISOR:BEGIN-->'
|
69
72
|
END_TOKEN = b'<!--XSUPERVISOR:END-->'
|
70
73
|
|
71
|
-
channel: ta.ClassVar[
|
74
|
+
channel: ta.ClassVar[ProcessOutputChannel]
|
72
75
|
|
73
76
|
def __init__(self, process, pid, data):
|
74
77
|
super().__init__()
|
ominfra/supervisor/http.py
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
# ruff: noqa: U006 UP007
|
2
|
+
import contextlib
|
2
3
|
import json
|
3
4
|
import socket
|
4
5
|
import typing as ta
|
5
6
|
|
6
7
|
from omlish.lite.check import check_not_none
|
8
|
+
from omlish.lite.contextmanagers import defer
|
7
9
|
from omlish.lite.fdio.corohttp import CoroHttpServerConnectionFdIoHandler
|
8
10
|
from omlish.lite.fdio.handlers import SocketFdIoHandler
|
9
11
|
from omlish.lite.http.handlers import HttpHandler
|
@@ -59,6 +61,8 @@ class HttpServer(HasDispatchers):
|
|
59
61
|
self,
|
60
62
|
handler: Handler,
|
61
63
|
addr: Address = Address(('localhost', 8000)),
|
64
|
+
*,
|
65
|
+
exit_stack: contextlib.ExitStack,
|
62
66
|
) -> None:
|
63
67
|
super().__init__()
|
64
68
|
|
@@ -69,6 +73,8 @@ class HttpServer(HasDispatchers):
|
|
69
73
|
|
70
74
|
self._conns: ta.List[CoroHttpServerConnectionFdIoHandler] = []
|
71
75
|
|
76
|
+
exit_stack.enter_context(defer(self._server.close)) # noqa
|
77
|
+
|
72
78
|
def get_dispatchers(self) -> Dispatchers:
|
73
79
|
l = []
|
74
80
|
for c in self._conns:
|
@@ -113,6 +119,7 @@ class SupervisorHttpHandler:
|
|
113
119
|
'processes': {
|
114
120
|
p.name: {
|
115
121
|
'pid': p.pid,
|
122
|
+
'state': p.state.name,
|
116
123
|
}
|
117
124
|
for p in g
|
118
125
|
},
|
ominfra/supervisor/inject.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# ruff: noqa: UP006 UP007
|
2
|
+
import contextlib
|
2
3
|
import dataclasses as dc
|
3
4
|
import typing as ta
|
4
5
|
|
@@ -56,6 +57,7 @@ class _FdIoPollerDaemonizeListener(DaemonizeListener):
|
|
56
57
|
|
57
58
|
|
58
59
|
def bind_server(
|
60
|
+
exit_stack: contextlib.ExitStack,
|
59
61
|
config: ServerConfig,
|
60
62
|
*,
|
61
63
|
server_epoch: ta.Optional[ServerEpoch] = None,
|
@@ -64,6 +66,8 @@ def bind_server(
|
|
64
66
|
lst: ta.List[InjectorBindingOrBindings] = [
|
65
67
|
inj.bind(config),
|
66
68
|
|
69
|
+
inj.bind(exit_stack),
|
70
|
+
|
67
71
|
inj.bind_array(DaemonizeListener),
|
68
72
|
inj.bind_array_type(DaemonizeListener, DaemonizeListeners),
|
69
73
|
|
@@ -119,8 +123,10 @@ def bind_server(
|
|
119
123
|
PollFdIoPoller,
|
120
124
|
SelectFdIoPoller,
|
121
125
|
]))
|
122
|
-
lst.
|
123
|
-
|
126
|
+
lst.extend([
|
127
|
+
inj.bind(poller_impl, key=FdIoPoller, singleton=True),
|
128
|
+
inj.bind(_FdIoPollerDaemonizeListener, array=True, singleton=True),
|
129
|
+
])
|
124
130
|
|
125
131
|
#
|
126
132
|
|
ominfra/supervisor/io.py
CHANGED
@@ -44,9 +44,9 @@ class IoManager(HasDispatchers):
|
|
44
44
|
)
|
45
45
|
|
46
46
|
timeout = 1 # this cannot be fewer than the smallest TickEvent (5)
|
47
|
-
|
47
|
+
|
48
48
|
polled = self._poller.poll(timeout)
|
49
|
-
|
49
|
+
|
50
50
|
if polled.msg is not None:
|
51
51
|
log.error(polled.msg)
|
52
52
|
if polled.exc is not None:
|
ominfra/supervisor/main.py
CHANGED
@@ -29,6 +29,7 @@
|
|
29
29
|
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
30
30
|
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
31
31
|
# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
32
|
+
import contextlib
|
32
33
|
import itertools
|
33
34
|
import os.path
|
34
35
|
import typing as ta
|
@@ -96,18 +97,20 @@ def main(
|
|
96
97
|
prepare=prepare_server_config,
|
97
98
|
)
|
98
99
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
100
|
+
with contextlib.ExitStack() as es:
|
101
|
+
injector = inj.create_injector(bind_server(
|
102
|
+
es,
|
103
|
+
config,
|
104
|
+
server_epoch=ServerEpoch(epoch),
|
105
|
+
inherited_fds=inherited_fds,
|
106
|
+
))
|
104
107
|
|
105
|
-
|
108
|
+
supervisor = injector[Supervisor]
|
106
109
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
110
|
+
try:
|
111
|
+
supervisor.main()
|
112
|
+
except ExitNow:
|
113
|
+
pass
|
111
114
|
|
112
115
|
if supervisor.state < SupervisorState.RESTARTING:
|
113
116
|
break
|
ominfra/supervisor/states.py
CHANGED
@@ -27,6 +27,17 @@ class ProcessState(enum.IntEnum):
|
|
27
27
|
return self in SIGNALABLE_STATES
|
28
28
|
|
29
29
|
|
30
|
+
# http://supervisord.org/subprocess.html
|
31
|
+
STATE_TRANSITIONS = {
|
32
|
+
ProcessState.STOPPED: (ProcessState.STARTING,),
|
33
|
+
ProcessState.STARTING: (ProcessState.RUNNING, ProcessState.BACKOFF, ProcessState.STOPPING),
|
34
|
+
ProcessState.RUNNING: (ProcessState.STOPPING, ProcessState.EXITED),
|
35
|
+
ProcessState.BACKOFF: (ProcessState.STARTING, ProcessState.FATAL),
|
36
|
+
ProcessState.STOPPING: (ProcessState.STOPPED,),
|
37
|
+
ProcessState.EXITED: (ProcessState.STARTING,),
|
38
|
+
ProcessState.FATAL: (ProcessState.STARTING,),
|
39
|
+
}
|
40
|
+
|
30
41
|
STOPPED_STATES = (
|
31
42
|
ProcessState.STOPPED,
|
32
43
|
ProcessState.EXITED,
|
ominfra/supervisor/supervisor.py
CHANGED
@@ -182,11 +182,29 @@ class Supervisor:
|
|
182
182
|
#
|
183
183
|
|
184
184
|
def _run_once(self) -> None:
|
185
|
-
|
186
|
-
|
187
|
-
|
185
|
+
if self._states.state < SupervisorState.RUNNING:
|
186
|
+
if not self._stopping:
|
187
|
+
# first time, set the stopping flag, do a notification and set stop_groups
|
188
|
+
self._stopping = True
|
189
|
+
self._stop_groups = sorted(self._process_groups)
|
190
|
+
self._event_callbacks.notify(SupervisorStoppingEvent())
|
191
|
+
|
192
|
+
self._ordered_stop_groups_phase_1()
|
193
|
+
|
194
|
+
if not self.shutdown_report():
|
195
|
+
# if there are no unstopped processes (we're done killing everything), it's OK to shutdown or reload
|
196
|
+
raise ExitNow
|
197
|
+
|
198
|
+
self._io.poll()
|
199
|
+
|
200
|
+
for group in sorted(self._process_groups):
|
201
|
+
for process in group:
|
202
|
+
process.transition()
|
203
|
+
|
188
204
|
self._reap()
|
205
|
+
|
189
206
|
self._signal_handler.handle_signals()
|
207
|
+
|
190
208
|
self._tick()
|
191
209
|
|
192
210
|
if self._states.state < SupervisorState.RUNNING:
|
@@ -208,38 +226,18 @@ class Supervisor:
|
|
208
226
|
# down, so push it back on to the end of the stop group queue
|
209
227
|
self._stop_groups.append(group)
|
210
228
|
|
211
|
-
|
212
|
-
sorted_groups = list(self._process_groups)
|
213
|
-
sorted_groups.sort()
|
214
|
-
|
215
|
-
if self._states.state < SupervisorState.RUNNING:
|
216
|
-
if not self._stopping:
|
217
|
-
# first time, set the stopping flag, do a notification and set stop_groups
|
218
|
-
self._stopping = True
|
219
|
-
self._stop_groups = sorted_groups[:]
|
220
|
-
self._event_callbacks.notify(SupervisorStoppingEvent())
|
221
|
-
|
222
|
-
self._ordered_stop_groups_phase_1()
|
223
|
-
|
224
|
-
if not self.shutdown_report():
|
225
|
-
# if there are no unstopped processes (we're done killing everything), it's OK to shutdown or reload
|
226
|
-
raise ExitNow
|
227
|
-
|
228
|
-
self._io.poll()
|
229
|
-
|
230
|
-
for group in sorted_groups:
|
231
|
-
for process in group:
|
232
|
-
process.transition()
|
229
|
+
#
|
233
230
|
|
234
231
|
def _reap(self, *, once: bool = False, depth: int = 0) -> None:
|
235
232
|
if depth >= 100:
|
236
233
|
return
|
237
234
|
|
238
235
|
wp = waitpid()
|
239
|
-
|
236
|
+
|
240
237
|
if wp is None or not wp.pid:
|
241
238
|
return
|
242
239
|
|
240
|
+
log.info(f'Waited pid: {wp}') # noqa
|
243
241
|
process = self._pid_history.get(wp.pid, None)
|
244
242
|
if process is None:
|
245
243
|
_, msg = decode_wait_status(wp.sts)
|
@@ -252,6 +250,8 @@ class Supervisor:
|
|
252
250
|
# keep reaping until no more kids to reap, but don't recurse infinitely
|
253
251
|
self._reap(once=False, depth=depth + 1)
|
254
252
|
|
253
|
+
#
|
254
|
+
|
255
255
|
def _tick(self, now: ta.Optional[float] = None) -> None:
|
256
256
|
"""Send one or more 'tick' events when the timeslice related to the period for the event type rolls over"""
|
257
257
|
|
ominfra/supervisor/types.py
CHANGED
@@ -7,6 +7,7 @@ from omlish.lite.fdio.handlers import FdIoHandler
|
|
7
7
|
|
8
8
|
from .configs import ProcessConfig
|
9
9
|
from .configs import ProcessGroupConfig
|
10
|
+
from .events import ProcessOutputChannel
|
10
11
|
from .states import ProcessState
|
11
12
|
from .states import SupervisorState
|
12
13
|
from .utils.collections import KeyedCollectionAccessors
|
@@ -71,7 +72,7 @@ class HasDispatchers(abc.ABC):
|
|
71
72
|
class ProcessDispatcher(FdIoHandler, abc.ABC):
|
72
73
|
@property
|
73
74
|
@abc.abstractmethod
|
74
|
-
def channel(self) ->
|
75
|
+
def channel(self) -> ProcessOutputChannel:
|
75
76
|
raise NotImplementedError
|
76
77
|
|
77
78
|
@property
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: ominfra
|
3
|
-
Version: 0.0.0.
|
3
|
+
Version: 0.0.0.dev132
|
4
4
|
Summary: ominfra
|
5
5
|
Author: wrmsr
|
6
6
|
License: BSD-3-Clause
|
@@ -12,8 +12,8 @@ Classifier: Operating System :: OS Independent
|
|
12
12
|
Classifier: Operating System :: POSIX
|
13
13
|
Requires-Python: >=3.12
|
14
14
|
License-File: LICENSE
|
15
|
-
Requires-Dist: omdev==0.0.0.
|
16
|
-
Requires-Dist: omlish==0.0.0.
|
15
|
+
Requires-Dist: omdev==0.0.0.dev132
|
16
|
+
Requires-Dist: omlish==0.0.0.dev132
|
17
17
|
Provides-Extra: all
|
18
18
|
Requires-Dist: paramiko~=3.5; extra == "all"
|
19
19
|
Requires-Dist: asyncssh~=2.18; extra == "all"
|
@@ -60,34 +60,34 @@ ominfra/pyremote/_runcommands.py,sha256=RKNvaHje-QGpvwwmFQ6OfEn3eEJ2c6zVUBYnDo2R
|
|
60
60
|
ominfra/pyremote/bootstrap.py,sha256=RvMO3YGaN1E4sgUi1JEtiPak8cjvqtc_vRCq1yqbeZg,3370
|
61
61
|
ominfra/pyremote/runcommands.py,sha256=bviS0_TDIoZVAe4h-_iavbvJtVSFu8lnk7fQ5iasCWE,1571
|
62
62
|
ominfra/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
63
|
-
ominfra/scripts/journald2aws.py,sha256=
|
64
|
-
ominfra/scripts/supervisor.py,sha256=
|
63
|
+
ominfra/scripts/journald2aws.py,sha256=o6WgI9R3MXdH4-BKUYAK2pcu3E5T0GAl5pggzR4Lhrk,131537
|
64
|
+
ominfra/scripts/supervisor.py,sha256=_n84Wz_L1QZmOTD3S9PGecmSt6OUPCO6K9lOu4VoByE,245533
|
65
65
|
ominfra/supervisor/LICENSE.txt,sha256=yvqaMNsDhWxziHa9ien6qCW1SkZv-DQlAg96XjfSee8,1746
|
66
66
|
ominfra/supervisor/__init__.py,sha256=Y3l4WY4JRi2uLG6kgbGp93fuGfkxkKwZDvhsa0Rwgtk,15
|
67
67
|
ominfra/supervisor/__main__.py,sha256=I0yFw-C08OOiZ3BF6lF1Oiv789EQXu-_j6whDhQUTEA,66
|
68
68
|
ominfra/supervisor/configs.py,sha256=OPtiL5_SjBYQ9xDqMgZvjoXgpleTxv4d3C4fD1X2Dz0,3966
|
69
69
|
ominfra/supervisor/dispatchers.py,sha256=dfjog5PyVAJaFzgFI0tpy38ZgDiFrewMjVi21ksrlAg,1007
|
70
|
-
ominfra/supervisor/dispatchersimpl.py,sha256=
|
71
|
-
ominfra/supervisor/events.py,sha256=
|
70
|
+
ominfra/supervisor/dispatchersimpl.py,sha256=q3dEyOHWTPKm28nmAGisjgIW1BX6O3-SzbYa7nWuTEs,11349
|
71
|
+
ominfra/supervisor/events.py,sha256=XGrtzHr1xm0dwjz329fn9eR0_Ap-LQL6Sk8LJ8eVDEo,6692
|
72
72
|
ominfra/supervisor/exceptions.py,sha256=Qbu211H3CLlSmi9LsSikOwrcL5HgJP9ugvcKWlGTAoI,750
|
73
73
|
ominfra/supervisor/groups.py,sha256=MBbsbt8Zh_WEYkGOr1KXa82gnPVw9wPB2lAnqhugXSc,2457
|
74
74
|
ominfra/supervisor/groupsimpl.py,sha256=nIrW4SmB0W6c2jOR_HhkfVcH4eHyLZnG1FJ0MCzc6mQ,2292
|
75
|
-
ominfra/supervisor/http.py,sha256=
|
76
|
-
ominfra/supervisor/inject.py,sha256=
|
77
|
-
ominfra/supervisor/io.py,sha256=
|
78
|
-
ominfra/supervisor/main.py,sha256=
|
75
|
+
ominfra/supervisor/http.py,sha256=WC7TD6ECD28GU83sUAd6Xau2pvYyZZmmFDgL-E4_Jd0,3472
|
76
|
+
ominfra/supervisor/inject.py,sha256=cH7ZV5KnOlHh90_S6YWlJsJCfM9Ve956GzEAhl4arx8,4627
|
77
|
+
ominfra/supervisor/io.py,sha256=_G66luJDF-i1JPM-qJiREMdmHk2ul7n-kjr7ex1sF6s,3200
|
78
|
+
ominfra/supervisor/main.py,sha256=oqaWOcnHJgaxNhjyphPgjaNjHPjDcx7kzYMjtZpwSxE,4253
|
79
79
|
ominfra/supervisor/pipes.py,sha256=2ZihNTnRNjnIPOtPbm3_pyqO15f7BNs7WnNtO5V8ahM,2231
|
80
80
|
ominfra/supervisor/privileges.py,sha256=kaRTHI7XjqzxEWCeHp3_0J0Vc4gSPugRbXEwxuw6MYE,2054
|
81
81
|
ominfra/supervisor/process.py,sha256=UaubVxsxVqDnbuWVpTH0DTGbJGLO0vGJ9mNcvy2kCXM,217
|
82
|
-
ominfra/supervisor/processimpl.py,sha256=
|
82
|
+
ominfra/supervisor/processimpl.py,sha256=BcvX-e03qjFSUogDUVmevwyizXS3kss3JY1QkHWjKWE,18689
|
83
83
|
ominfra/supervisor/setup.py,sha256=7HwwwI-WT_Z0WjZ9_l5Orr4K298nKKhQ1f_ZgGsi9TU,622
|
84
84
|
ominfra/supervisor/setupimpl.py,sha256=88h3oYsdJ0LAo7sZZZGRQti14oQay3b-0Vd_h3Cl108,9638
|
85
85
|
ominfra/supervisor/signals.py,sha256=jY52naUifcAjd6nICTP1ZW3IQSPsHB4cvbsJo8_QV_U,2196
|
86
86
|
ominfra/supervisor/spawning.py,sha256=i1k3tmqWyU-KIN7kel-JVxTVGnLiTIVmZzlstJSZpjM,622
|
87
87
|
ominfra/supervisor/spawningimpl.py,sha256=Pkp6mefJhOGCCj5T2I1jXEsVp15g9KMKbANYgoX-ws8,11162
|
88
|
-
ominfra/supervisor/states.py,sha256=
|
89
|
-
ominfra/supervisor/supervisor.py,sha256=
|
90
|
-
ominfra/supervisor/types.py,sha256=
|
88
|
+
ominfra/supervisor/states.py,sha256=x1trJQbItkSegOmotpn5YNcZMLbBL8I3GmhvFCQl4Oo,1400
|
89
|
+
ominfra/supervisor/supervisor.py,sha256=5V5DJHThPzJmctO4_UZV5-vx0ieT43TIcL_ZIl6T0xQ,9342
|
90
|
+
ominfra/supervisor/types.py,sha256=n4AILLzdeavM5ejOFKP-rPRNK87UV8aFyoeRKPVDxxg,4017
|
91
91
|
ominfra/supervisor/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
92
92
|
ominfra/supervisor/utils/collections.py,sha256=vcfmVYS4QngMdtEI1DvdRIcubmy55Wj40NCzW27_rIY,1361
|
93
93
|
ominfra/supervisor/utils/diag.py,sha256=ujz4gkW7p3wmbaKFM8Hz5eHEwpoUkbB8JeDvcHilCz0,705
|
@@ -103,9 +103,9 @@ ominfra/tailscale/api.py,sha256=C5-t_b6jZXUWcy5k8bXm7CFnk73pSdrlMOgGDeGVrpw,1370
|
|
103
103
|
ominfra/tailscale/cli.py,sha256=DSGp4hn5xwOW-l_u_InKlSF6kIobxtUtVssf_73STs0,3567
|
104
104
|
ominfra/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
105
105
|
ominfra/tools/listresources.py,sha256=4qVg5txsb10EHhvqXXeM6gJ2jx9LbroEnPydDv1uXs0,6176
|
106
|
-
ominfra-0.0.0.
|
107
|
-
ominfra-0.0.0.
|
108
|
-
ominfra-0.0.0.
|
109
|
-
ominfra-0.0.0.
|
110
|
-
ominfra-0.0.0.
|
111
|
-
ominfra-0.0.0.
|
106
|
+
ominfra-0.0.0.dev132.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
|
107
|
+
ominfra-0.0.0.dev132.dist-info/METADATA,sha256=vq41f4P1jKGnX9UuGdMibA-bayJhVbb8rk_vZQw0-r8,731
|
108
|
+
ominfra-0.0.0.dev132.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
109
|
+
ominfra-0.0.0.dev132.dist-info/entry_points.txt,sha256=kgecQ2MgGrM9qK744BoKS3tMesaC3yjLnl9pa5CRczg,37
|
110
|
+
ominfra-0.0.0.dev132.dist-info/top_level.txt,sha256=E-b2OHkk_AOBLXHYZQ2EOFKl-_6uOGd8EjeG-Zy6h_w,8
|
111
|
+
ominfra-0.0.0.dev132.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|