Skip to content

Commit 0bde29d

Browse files
authored
Merge pull request #125 from julien6387/dev-0.19
Dev 0.18.3
2 parents c1f3b4f + f7c5626 commit 0bde29d

File tree

5 files changed

+78
-10
lines changed

5 files changed

+78
-10
lines changed

CHANGES.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
11
# Change Log
22

3+
## 0.18.3 (2024-06-05)
4+
5+
* Recreate the internal proxy to the local Supervisor if older than 20 minutes, in order to palliate the closure
6+
of all channels inactive during 30 minutes in the Supervisor HTTP server.
7+
This fixes a rare bug that has been introduced in **Supvisors** 0.17, and whose occurrence has greatly
8+
increased after the refactoring of the **Supvisors** 0.18 internal communications.
9+
10+
* Swap Memory and Network statistics cards in the **Supvisors** Web UI.
11+
12+
313
## 0.18.2 (2024-05-27)
414

5-
* Handle PermissionError exception when trying to get disk usage.
15+
* Handle the `PermissionError` exception when trying to get disk usage.
616

717
* Update CSS for overflow parts in the **Supvisors** Web UI.
818

supvisors/internal_com/supervisorproxy.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import json
1818
import queue
1919
import threading
20+
import time
2021
import traceback
2122
from enum import Enum
2223
from http.client import HTTPException
@@ -36,6 +37,10 @@
3637
# List of keys useful to build a SupvisorsState event
3738
StateModesKeys = ['fsm_statecode', 'discovery_mode', 'master_identifier', 'starting_jobs', 'stopping_jobs']
3839

40+
# life expectation for the local proxy
41+
# Supervisor close any HTTP channel after 30 minutes without activity
42+
LOCAL_PROXY_DURATION = 20 * 60
43+
3944

4045
class InternalEventHeaders(Enum):
4146
""" Event type for deferred XML-RPCs. """
@@ -55,7 +60,8 @@ def __init__(self, status: SupvisorsInstanceStatus, supvisors: Any):
5560
self.status: SupvisorsInstanceStatus = status
5661
self.supvisors = supvisors
5762
# create an XML-RPC client to the local Supervisor instance
58-
self.proxy = self._get_proxy()
63+
self._proxy = self._get_proxy()
64+
self.last_used: float = time.monotonic()
5965

6066
@property
6167
def logger(self) -> Logger:
@@ -67,6 +73,23 @@ def local_identifier(self) -> str:
6773
""" Get the local Supvisors instance identifier. """
6874
return self.supvisors.mapper.local_identifier
6975

76+
@property
77+
def proxy(self) -> Logger:
78+
""" Get the Supervisor proxy.
79+
80+
WARN: The proxy to the local Supervisor is a LOT less used than the others and is really subject to be broken
81+
by the http_channel.kill_zombies (supervisor/medusa/http_server.py) that will close the channel after
82+
30 minutes of inactivity (magic number).
83+
Let's re-create the local proxy once every 20 minutes.
84+
All other proxies will be maintained through to the TICK publication.
85+
"""
86+
if self.status.supvisors_id.identifier == self.local_identifier:
87+
if time.monotonic() - self.last_used > LOCAL_PROXY_DURATION:
88+
self.logger.debug(f'SupervisorProxy.proxy: recreate local Supervisor proxy')
89+
self._proxy = self._get_proxy()
90+
self.last_used = time.monotonic()
91+
return self._proxy
92+
7093
def _get_proxy(self):
7194
""" Get the proxy corresponding to the Supervisor identifier. """
7295
instance_id = self.status.supvisors_id
@@ -81,6 +104,9 @@ def _get_origin(self, from_identifier: str) -> Tuple[str, Ipv4Address]:
81104

82105
def xml_rpc(self, fct_name: str, fct, args):
83106
""" Common exception handling on XML-RPC methods. """
107+
# reset the proxy usage time
108+
self.last_used = time.monotonic()
109+
# call the XML-RPC
84110
try:
85111
return fct(*args)
86112
except RPCError as exc:
@@ -92,6 +118,7 @@ def xml_rpc(self, fct_name: str, fct, args):
92118
except (OSError, HTTPException) as exc:
93119
# transport issue due to network or remote Supervisor failure (includes a bunch of exceptions, such as
94120
# socket.gaierror, ConnectionResetError, ConnectionRefusedError, CannotSendRequest, IncompleteRead, etc.)
121+
# also raised if the HTTP channel has been closed by the Supervisor kill_zombies (30 minutes inactivity)
95122
# the proxy is not operational - error log only if instance is active
96123
log_level = LevelsByName.ERRO if self.status.has_active_state() else LevelsByName.DEBG
97124
message = f'SupervisorProxy.xml_rpc: Supervisor={self.status.usage_identifier} not reachable - {str(exc)}'

supvisors/tests/test_supervisorproxy.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
import http.client
1818
import socket
19-
import time
2019
from unittest.mock import call, patch, Mock, DEFAULT
2120

2221
import pytest
@@ -66,13 +65,33 @@ def test_proxy_creation(mocked_rpc, proxy, supvisors):
6665
assert proxy.supvisors is supvisors
6766
assert proxy.status is supvisors.context.instances['10.0.0.1:25000']
6867
assert proxy.proxy is not None
68+
assert 0.0 < proxy.last_used < time.monotonic()
6969
assert proxy.logger is supvisors.logger
7070
assert proxy.local_identifier == supvisors.mapper.local_identifier
7171
assert mocked_rpc.call_args_list == [call({'SUPERVISOR_SERVER_URL': 'http://10.0.0.1:25000',
7272
'SUPERVISOR_USERNAME': 'user',
7373
'SUPERVISOR_PASSWORD': 'p@$$w0rd'})]
7474

7575

76+
def test_proxy_proxy(supvisors, proxy):
77+
""" Test the SupvisorsProxy proxy property. """
78+
# test with non-local proxy
79+
assert proxy.status.supvisors_id.identifier != proxy.local_identifier
80+
ref_proxy = proxy._proxy
81+
ref_usage = proxy.last_used
82+
assert proxy.proxy is ref_proxy
83+
assert proxy.last_used == ref_usage
84+
# test with local proxy and recent usage
85+
proxy.status = supvisors.context.local_status
86+
assert proxy.status.supvisors_id.identifier == proxy.local_identifier
87+
assert proxy.proxy is ref_proxy
88+
assert proxy.last_used == ref_usage
89+
# test with local proxy and old usage (cannot test everything due to patch)
90+
proxy.last_used = time.monotonic() - LOCAL_PROXY_DURATION - 1
91+
assert proxy.proxy
92+
assert proxy.last_used > ref_usage
93+
94+
7695
def test_get_origin(supvisors, proxy):
7796
""" Test the SupervisorProxy._get_origin method. """
7897
local_instance = supvisors.mapper.local_instance
@@ -82,28 +101,39 @@ def test_get_origin(supvisors, proxy):
82101

83102
def test_proxy_xml_rpc(supvisors, proxy):
84103
""" Test the SupervisorProxy function to send any XML-RPC to a Supervisor instance. """
104+
ref_usage = proxy.last_used
85105
mocked_fct = Mock()
86106
# test no error
87107
proxy.xml_rpc('normal', mocked_fct, ())
88108
assert mocked_fct.call_args_list == [call()]
109+
assert proxy.last_used > ref_usage
110+
ref_usage = proxy.last_used
89111
mocked_fct.reset_mock()
90112
proxy.xml_rpc('normal', mocked_fct, ('hello',))
91113
assert mocked_fct.call_args_list == [call('hello')]
114+
assert proxy.last_used > ref_usage
115+
ref_usage = proxy.last_used
92116
mocked_fct.reset_mock()
93117
proxy.xml_rpc('normal', mocked_fct, ('hello', 28))
94118
assert mocked_fct.call_args_list == [call('hello', 28)]
119+
assert proxy.last_used > ref_usage
120+
ref_usage = proxy.last_used
95121
mocked_fct.reset_mock()
96122
# test minor exception (remote Supvisors instance is operational)
97123
mocked_fct.side_effect = RPCError(code=58)
98124
proxy.xml_rpc('normal', mocked_fct, ('hello', 28))
99125
assert mocked_fct.call_args_list == [call('hello', 28)]
126+
assert proxy.last_used > ref_usage
127+
ref_usage = proxy.last_used
100128
mocked_fct.reset_mock()
101129
# test major exception (remote Supvisors instance is NOT operational)
102130
for exc_class in [OSError, HTTPException, xmlrpclib.Fault(77, 'fault'), KeyError, ValueError, TypeError]:
103131
mocked_fct.side_effect = exc_class
104132
with pytest.raises(SupervisorProxyException):
105133
proxy.xml_rpc('normal', mocked_fct, ('hello',))
106134
assert mocked_fct.call_args_list == [call('hello')]
135+
assert proxy.last_used > ref_usage
136+
ref_usage = proxy.last_used
107137
mocked_fct.reset_mock()
108138

109139

supvisors/ui/css/host_instance.css

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060
/* Statistics part */
6161
#rsc_grid {
6262
display: grid;
63-
width: 95%;
63+
width: 98%;
6464
height: 83%;
6565
grid-template-rows: 49% 49%;
6666
grid-gap: 10px;
@@ -74,13 +74,13 @@
7474
}
7575

7676
#mem_stats {
77-
grid-column: 2;
78-
grid-row: 1;
77+
grid-column: 1;
78+
grid-row: 2;
7979
}
8080

8181
#net_io_stats {
82-
grid-column: 1;
83-
grid-row: 2;
82+
grid-column: 2;
83+
grid-row: 1;
8484
}
8585

8686
#disk_io_stats {
@@ -115,7 +115,8 @@
115115
.table_stats {
116116
display: flex;
117117
height: 90%;
118-
overflow: clip auto;
118+
overflow-x: clip;
119+
overflow-y: auto;
119120
margin: 0 auto;
120121
padding-right: 15px;
121122
}

supvisors/version.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
version=0.18.2
1+
version=0.18.3

0 commit comments

Comments
 (0)