wolsocketproxy 0.3.0__tar.gz → 0.3.1__tar.gz

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.
Files changed (18) hide show
  1. {wolsocketproxy-0.3.0/src/wolsocketproxy.egg-info → wolsocketproxy-0.3.1}/PKG-INFO +7 -2
  2. {wolsocketproxy-0.3.0 → wolsocketproxy-0.3.1}/README.md +6 -1
  3. {wolsocketproxy-0.3.0 → wolsocketproxy-0.3.1}/pyproject.toml +2 -1
  4. wolsocketproxy-0.3.1/src/wolsocketproxy/common.py +1 -0
  5. {wolsocketproxy-0.3.0 → wolsocketproxy-0.3.1}/src/wolsocketproxy/keepalive.py +7 -8
  6. {wolsocketproxy-0.3.0 → wolsocketproxy-0.3.1}/src/wolsocketproxy/proxy.py +107 -7
  7. {wolsocketproxy-0.3.0 → wolsocketproxy-0.3.1/src/wolsocketproxy.egg-info}/PKG-INFO +7 -2
  8. {wolsocketproxy-0.3.0 → wolsocketproxy-0.3.1}/src/wolsocketproxy.egg-info/SOURCES.txt +1 -0
  9. {wolsocketproxy-0.3.0 → wolsocketproxy-0.3.1}/LICENSE +0 -0
  10. {wolsocketproxy-0.3.0 → wolsocketproxy-0.3.1}/setup.cfg +0 -0
  11. {wolsocketproxy-0.3.0 → wolsocketproxy-0.3.1}/src/wolsocketproxy/__init__.py +0 -0
  12. {wolsocketproxy-0.3.0 → wolsocketproxy-0.3.1}/src/wolsocketproxy/__main__.py +0 -0
  13. {wolsocketproxy-0.3.0 → wolsocketproxy-0.3.1}/src/wolsocketproxy/monitor.py +0 -0
  14. {wolsocketproxy-0.3.0 → wolsocketproxy-0.3.1}/src/wolsocketproxy/utils.py +0 -0
  15. {wolsocketproxy-0.3.0 → wolsocketproxy-0.3.1}/src/wolsocketproxy.egg-info/dependency_links.txt +0 -0
  16. {wolsocketproxy-0.3.0 → wolsocketproxy-0.3.1}/src/wolsocketproxy.egg-info/entry_points.txt +0 -0
  17. {wolsocketproxy-0.3.0 → wolsocketproxy-0.3.1}/src/wolsocketproxy.egg-info/requires.txt +0 -0
  18. {wolsocketproxy-0.3.0 → wolsocketproxy-0.3.1}/src/wolsocketproxy.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wolsocketproxy
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: A socket proxy with wake-on-lan feature.
5
5
  Author-email: Song Fuchang <song.fc@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -100,7 +100,10 @@ The `wolsocketproxy.conf` has the following structure:
100
100
  "online_check_timeout": 300, // Timeout when checking machine is online, seconds, optional, default is 60
101
101
  "online_check_http_expected_code": 200, // Expected HTTP status code when using "http" method, optional, default is 200
102
102
  "ipmi_force_reset_if_power_up_failed": true, // Use IPMI power reset if this machine is not online after timeout, optional, default is false
103
- "ipmi_max_reset_try_count": 3 // Max IPMI power reset retry count before giving-up, optional, default is 3
103
+ "ipmi_max_reset_try_count": 3, // Max IPMI power reset retry count before giving-up, optional, default is 3
104
+ "keep_alive_mode": true, // Indicates whether this machine has a keep-alive daemon, optional, default is false
105
+ // If you enable this, wolsocketproxy will send a request when there is any traffic
106
+ "keep_alive_mode_base_url": "http://192.168.1.124:8080" // Keep-alive daemon provided URL, optional
104
107
  },
105
108
  // ... more machines ...
106
109
  },
@@ -162,3 +165,5 @@ The config file `wolsocketproxy.conf` should look like the following:
162
165
  You need to periodically send an HTTP GET request to `/watchdog/feed` at the `listen_port` in `watchdog_feed_interval` time,
163
166
  or the special process will be killed.
164
167
  You can combine this mode with `circadian`'s `process_block` config to keep it from auto-suspending.
168
+
169
+ If you enable `keep_alive_mode` in proxy mode's config, the proxy will send requests to `/watchdog/feed` of this machine whenever there is any traffic towards this machine through it.
@@ -69,7 +69,10 @@ The `wolsocketproxy.conf` has the following structure:
69
69
  "online_check_timeout": 300, // Timeout when checking machine is online, seconds, optional, default is 60
70
70
  "online_check_http_expected_code": 200, // Expected HTTP status code when using "http" method, optional, default is 200
71
71
  "ipmi_force_reset_if_power_up_failed": true, // Use IPMI power reset if this machine is not online after timeout, optional, default is false
72
- "ipmi_max_reset_try_count": 3 // Max IPMI power reset retry count before giving-up, optional, default is 3
72
+ "ipmi_max_reset_try_count": 3, // Max IPMI power reset retry count before giving-up, optional, default is 3
73
+ "keep_alive_mode": true, // Indicates whether this machine has a keep-alive daemon, optional, default is false
74
+ // If you enable this, wolsocketproxy will send a request when there is any traffic
75
+ "keep_alive_mode_base_url": "http://192.168.1.124:8080" // Keep-alive daemon provided URL, optional
73
76
  },
74
77
  // ... more machines ...
75
78
  },
@@ -131,3 +134,5 @@ The config file `wolsocketproxy.conf` should look like the following:
131
134
  You need to periodically send an HTTP GET request to `/watchdog/feed` at the `listen_port` in `watchdog_feed_interval` time,
132
135
  or the special process will be killed.
133
136
  You can combine this mode with `circadian`'s `process_block` config to keep it from auto-suspending.
137
+
138
+ If you enable `keep_alive_mode` in proxy mode's config, the proxy will send requests to `/watchdog/feed` of this machine whenever there is any traffic towards this machine through it.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "wolsocketproxy"
7
- version = "0.3.0"
7
+ version = "0.3.1"
8
8
  description = "A socket proxy with wake-on-lan feature."
9
9
  authors = [
10
10
  {name = "Song Fuchang", email = "song.fc@gmail.com"}
@@ -72,4 +72,5 @@ lint.ignore = [
72
72
  "ISC001",
73
73
  "TRY003",
74
74
  "TRY201",
75
+ "PLR0913",
75
76
  ]
@@ -0,0 +1 @@
1
+ URL_WATCHDOG_FEED = "/watchdog/feed"
@@ -12,6 +12,8 @@ from aiohttp import web
12
12
  from aiohttp.web import Request, Response
13
13
  from setproctitle import setproctitle
14
14
 
15
+ from wolsocketproxy.common import URL_WATCHDOG_FEED
16
+
15
17
 
16
18
  @dataclass
17
19
  class KeepAliveConfig:
@@ -41,11 +43,7 @@ class KeepAliveDaemon:
41
43
 
42
44
  self._web_app = web.Application()
43
45
 
44
- self._web_app.add_routes(
45
- [
46
- web.get("/watchdog/feed", self._handle_watchdog_feed)
47
- ]
48
- )
46
+ self._web_app.add_routes([web.get(URL_WATCHDOG_FEED, self._handle_watchdog_feed)])
49
47
 
50
48
  async def _watchdog_timer(self) -> None:
51
49
  while True:
@@ -97,8 +95,7 @@ class KeepAliveDaemon:
97
95
  return True
98
96
 
99
97
  self._log.info(
100
- "Started special process with name %s, PID %d",
101
- self._config.special_process_name, self._special_process_id
98
+ "Started special process with name %s, PID %d", self._config.special_process_name, self._special_process_id
102
99
  )
103
100
 
104
101
  return False
@@ -123,7 +120,9 @@ class KeepAliveDaemon:
123
120
 
124
121
  self._log.info(
125
122
  "Keep-alive daemon started at %s:%d, watchdog feed interval %ds.",
126
- self._config.listen_address, self._config.listen_port, self._config.watchdog_feed_interval
123
+ self._config.listen_address,
124
+ self._config.listen_port,
125
+ self._config.watchdog_feed_interval,
127
126
  )
128
127
 
129
128
  web.run_app(self._web_app, host=self._config.listen_address, port=self._config.listen_port)
@@ -3,11 +3,14 @@ import contextlib
3
3
  import logging
4
4
  from dataclasses import dataclass
5
5
  from logging import Logger
6
+ from threading import Thread
6
7
  from typing import Any, Literal, override
7
8
 
9
+ import aiohttp
8
10
  import wakeonlan
9
11
  from redfish.rest.v1 import HttpClient, redfish_client
10
12
 
13
+ from wolsocketproxy.common import URL_WATCHDOG_FEED
11
14
  from wolsocketproxy.monitor import Monitor, MonitorConfig
12
15
  from wolsocketproxy.utils import perform_ipmi_action
13
16
 
@@ -29,6 +32,10 @@ class MachineConfig:
29
32
  online_check_http_expected_code: int = 200
30
33
  online_check_timeout: int = 60
31
34
 
35
+ keep_alive_mode: bool = False
36
+ keep_alive_mode_base_url: str | None = None
37
+ keep_alive_min_interval: int = 1
38
+
32
39
 
33
40
  @dataclass
34
41
  class ProxyRoute:
@@ -56,6 +63,42 @@ class ProxyConfig:
56
63
  ipmi_configs: list[IPMIConfig] | None = None
57
64
 
58
65
 
66
+ class TargetKeepAliveSender:
67
+ _target_url: str
68
+ _keep_alive_min_interval: int
69
+ _loop: asyncio.AbstractEventLoop
70
+ _queue: asyncio.Queue
71
+
72
+ def __init__(self, target_base_url: str, keep_alive_min_interval: int) -> None:
73
+ self._target_url = target_base_url.removesuffix("/") + URL_WATCHDOG_FEED
74
+ self._keep_alive_min_interval = keep_alive_min_interval
75
+
76
+ self._loop = asyncio.new_event_loop()
77
+ self._queue = asyncio.Queue(1)
78
+
79
+ def _loop() -> None:
80
+ asyncio.set_event_loop(self._loop)
81
+ self._loop.run_until_complete(self._send_worker())
82
+
83
+ Thread(target=_loop, daemon=True).start()
84
+
85
+ async def _send_worker(self) -> None:
86
+ while True:
87
+ await self._queue.get()
88
+
89
+ async with aiohttp.request("GET", self._target_url) as resp:
90
+ await resp.json()
91
+
92
+ await asyncio.sleep(self._keep_alive_min_interval)
93
+
94
+ def schedule_send(self) -> None:
95
+ def _no_exception_put() -> None:
96
+ with contextlib.suppress(asyncio.QueueFull):
97
+ self._queue.put_nowait(1)
98
+
99
+ self._loop.call_soon_threadsafe(_no_exception_put)
100
+
101
+
59
102
  class ProxyUdpProtocol(asyncio.DatagramProtocol):
60
103
  _proxy: "Proxy"
61
104
  _monitor: Monitor
@@ -63,16 +106,24 @@ class ProxyUdpProtocol(asyncio.DatagramProtocol):
63
106
  _target_machine_name: str
64
107
  _target_address: str
65
108
  _target_port: int
109
+ _target_keep_alive_sender: TargetKeepAliveSender | None = None
66
110
  _target_pair: tuple[str, int]
67
111
 
68
112
  def __init__(
69
- self, proxy: "Proxy", monitor: Monitor, target_machine_name: str, target_address: str, target_port: int
113
+ self,
114
+ proxy: "Proxy",
115
+ monitor: Monitor,
116
+ target_machine_name: str,
117
+ target_address: str,
118
+ target_port: int,
119
+ target_keep_alive_sender: TargetKeepAliveSender | None = None,
70
120
  ) -> None:
71
121
  self._proxy = proxy
72
122
  self._monitor = monitor
73
123
  self._target_machine_name = target_machine_name
74
124
  self._target_address = target_address
75
125
  self._target_port = target_port
126
+ self._target_keep_alive_sender = target_keep_alive_sender
76
127
  self._target_pair = (target_address, target_port)
77
128
 
78
129
  @override
@@ -92,6 +143,9 @@ class ProxyUdpProtocol(asyncio.DatagramProtocol):
92
143
 
93
144
  self._transport.sendto(data, self._target_pair)
94
145
 
146
+ if self._target_keep_alive_sender is not None:
147
+ self._target_keep_alive_sender.schedule_send()
148
+
95
149
 
96
150
  class Proxy:
97
151
  _log: Logger = logging.getLogger()
@@ -180,9 +234,23 @@ class Proxy:
180
234
 
181
235
  def __create_tcp_route(self, route: ProxyRoute) -> None:
182
236
  assert route.target_machine_name is not None
237
+ machine_config = self._machines[route.target_machine_name]
238
+ target_keep_alive_sender = None
239
+
240
+ if machine_config.keep_alive_mode:
241
+ assert machine_config.keep_alive_mode_base_url is not None
242
+
243
+ target_keep_alive_sender = TargetKeepAliveSender(
244
+ machine_config.keep_alive_mode_base_url, machine_config.keep_alive_min_interval
245
+ )
183
246
 
184
247
  cr = asyncio.start_server(
185
- self.__make_tcp_route_handler(route.target_machine_name, route.target_address, route.target_port),
248
+ self.__make_tcp_route_handler(
249
+ route.target_machine_name,
250
+ route.target_address,
251
+ route.target_port,
252
+ target_keep_alive_sender,
253
+ ),
186
254
  route.local_address,
187
255
  route.local_port,
188
256
  )
@@ -190,16 +258,31 @@ class Proxy:
190
258
  loop = asyncio.get_event_loop()
191
259
  loop.run_until_complete(cr)
192
260
 
193
- async def __pipe(self, target_address: str, reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None:
261
+ async def __pipe(
262
+ self,
263
+ target_address: str,
264
+ reader: asyncio.StreamReader,
265
+ writer: asyncio.StreamWriter,
266
+ target_keep_alive_sender: TargetKeepAliveSender | None = None,
267
+ ) -> None:
194
268
  try:
195
269
  while not reader.at_eof():
196
270
  writer.write(await reader.read(2048))
271
+
272
+ if target_keep_alive_sender is not None:
273
+ target_keep_alive_sender.schedule_send()
197
274
  except ConnectionResetError:
198
275
  self._log.warning("Connection reset by target %s", target_address)
199
276
  finally:
200
277
  writer.close()
201
278
 
202
- def __make_tcp_route_handler(self, target_machine_name: str, target_address: str, target_port: int) -> Any: # noqa: ANN401
279
+ def __make_tcp_route_handler(
280
+ self,
281
+ target_machine_name: str,
282
+ target_address: str,
283
+ target_port: int,
284
+ target_keep_alive_sender: TargetKeepAliveSender | None = None,
285
+ ) -> Any: # noqa: ANN401
203
286
  async def handler(local_reader: asyncio.StreamReader, local_writer: asyncio.StreamWriter) -> None:
204
287
  if not self._monitor.is_available(target_machine_name):
205
288
  await self._wake_up_target(target_machine_name)
@@ -211,8 +294,8 @@ class Proxy:
211
294
  self._log.error("Unable to open connection to %s:%d", target_address, target_port)
212
295
  raise e
213
296
 
214
- send_pipe = self.__pipe(target_address, local_reader, target_writer)
215
- recv_pipe = self.__pipe(target_address, target_reader, local_writer)
297
+ send_pipe = self.__pipe(target_address, local_reader, target_writer, target_keep_alive_sender)
298
+ recv_pipe = self.__pipe(target_address, target_reader, local_writer, target_keep_alive_sender)
216
299
  await asyncio.gather(send_pipe, recv_pipe)
217
300
 
218
301
  return handler
@@ -221,10 +304,27 @@ class Proxy:
221
304
  target_machine_name = route.target_machine_name
222
305
  assert target_machine_name is not None
223
306
 
307
+ machine_config = self._machines[target_machine_name]
308
+ target_keep_alive_sender = None
309
+
310
+ if machine_config.keep_alive_mode:
311
+ assert machine_config.keep_alive_mode_base_url is not None
312
+
313
+ target_keep_alive_sender = TargetKeepAliveSender(
314
+ machine_config.keep_alive_mode_base_url, machine_config.keep_alive_min_interval
315
+ )
316
+
224
317
  loop = asyncio.get_event_loop()
225
318
 
226
319
  cr = loop.create_datagram_endpoint(
227
- lambda: ProxyUdpProtocol(self, self._monitor, target_machine_name, route.target_address, route.target_port),
320
+ lambda: ProxyUdpProtocol(
321
+ self,
322
+ self._monitor,
323
+ target_machine_name,
324
+ route.target_address,
325
+ route.target_port,
326
+ target_keep_alive_sender,
327
+ ),
228
328
  local_addr=(route.local_address, route.local_port),
229
329
  )
230
330
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wolsocketproxy
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: A socket proxy with wake-on-lan feature.
5
5
  Author-email: Song Fuchang <song.fc@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -100,7 +100,10 @@ The `wolsocketproxy.conf` has the following structure:
100
100
  "online_check_timeout": 300, // Timeout when checking machine is online, seconds, optional, default is 60
101
101
  "online_check_http_expected_code": 200, // Expected HTTP status code when using "http" method, optional, default is 200
102
102
  "ipmi_force_reset_if_power_up_failed": true, // Use IPMI power reset if this machine is not online after timeout, optional, default is false
103
- "ipmi_max_reset_try_count": 3 // Max IPMI power reset retry count before giving-up, optional, default is 3
103
+ "ipmi_max_reset_try_count": 3, // Max IPMI power reset retry count before giving-up, optional, default is 3
104
+ "keep_alive_mode": true, // Indicates whether this machine has a keep-alive daemon, optional, default is false
105
+ // If you enable this, wolsocketproxy will send a request when there is any traffic
106
+ "keep_alive_mode_base_url": "http://192.168.1.124:8080" // Keep-alive daemon provided URL, optional
104
107
  },
105
108
  // ... more machines ...
106
109
  },
@@ -162,3 +165,5 @@ The config file `wolsocketproxy.conf` should look like the following:
162
165
  You need to periodically send an HTTP GET request to `/watchdog/feed` at the `listen_port` in `watchdog_feed_interval` time,
163
166
  or the special process will be killed.
164
167
  You can combine this mode with `circadian`'s `process_block` config to keep it from auto-suspending.
168
+
169
+ If you enable `keep_alive_mode` in proxy mode's config, the proxy will send requests to `/watchdog/feed` of this machine whenever there is any traffic towards this machine through it.
@@ -3,6 +3,7 @@ README.md
3
3
  pyproject.toml
4
4
  src/wolsocketproxy/__init__.py
5
5
  src/wolsocketproxy/__main__.py
6
+ src/wolsocketproxy/common.py
6
7
  src/wolsocketproxy/keepalive.py
7
8
  src/wolsocketproxy/monitor.py
8
9
  src/wolsocketproxy/proxy.py
File without changes
File without changes