Skip to content

Commit b9d6a00

Browse files
Merge pull request ceph#54984 from NitzanMordhai/wip-nitzan-restful-un-boundary-keep-requests
mgr/rest: Trim requests array and limit size
2 parents a61eabe + dd7e8bb commit b9d6a00

File tree

3 files changed

+43
-5
lines changed

3 files changed

+43
-5
lines changed

PendingReleaseNotes

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,11 @@ CephFS: Disallow delegating preallocated inode ranges to clients. Config
199199
default json format produces a rather massive output in large clusters and
200200
isn't scalable. So we have removed the 'network_ping_times' section from
201201
the output. Details in the tracker: https://tracker.ceph.com/issues/57460
202+
* mgr/REST: The REST manager module will trim requests based on the 'max_requests' option.
203+
Without this feature, and in the absence of manual deletion of old requests,
204+
the accumulation of requests in the array can lead to Out Of Memory (OOM) issues,
205+
resulting in the Manager crashing.
206+
202207
* CephFS: The `subvolume snapshot clone` command now depends on the config option
203208
`snapshot_clone_no_wait` which is used to reject the clone operation when
204209
all the cloner threads are busy. This config option is enabled by default which means

doc/mgr/restful.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,19 @@ If the port is not configured, *restful* will bind to port ``8003``.
7777
If the address it not configured, the *restful* will bind to ``::``,
7878
which corresponds to all available IPv4 and IPv6 addresses.
7979

80+
Configuring max_request
81+
---------------------------
82+
83+
The maximum request size can be configured via a central configuration
84+
option::
85+
86+
ceph config set mgr mgr/restful/$name/max_requests $NUM
87+
88+
where ``$name`` is the ID of the ceph-mgr daemon (usually the hostname).
89+
90+
.. mgr_module:: restful
91+
.. confval:: max_requests
92+
8093
.. _creating-an-api-user:
8194

8295
Creating an API User

src/pybind/mgr/restful/module.py

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import traceback
1313
import socket
1414
import fcntl
15+
from typing import cast
1516

1617
from . import common
1718
from . import context
@@ -23,7 +24,7 @@
2324
from werkzeug.serving import make_server, make_ssl_devcert
2425

2526
from .hooks import ErrorHook
26-
from mgr_module import MgrModule, CommandResult, NotifyType
27+
from mgr_module import MgrModule, CommandResult, NotifyType, Option
2728
from mgr_util import build_url
2829

2930

@@ -193,10 +194,18 @@ def __json__(self):
193194

194195
class Module(MgrModule):
195196
MODULE_OPTIONS = [
196-
{'name': 'server_addr'},
197-
{'name': 'server_port'},
198-
{'name': 'key_file'},
199-
{'name': 'enable_auth', 'type': 'bool', 'default': True},
197+
Option(name='server_addr'),
198+
Option(name='server_port'),
199+
Option(name='key_file'),
200+
Option(name='enable_auth',
201+
type='bool',
202+
default=True),
203+
Option(name='max_requests',
204+
type='int',
205+
default=500,
206+
desc='Maximum number of requests to keep in memory. '
207+
' When new request comes in, the oldest request will be removed if the number of requests exceeds the max request number.'
208+
'if un-finished request is removed, error message will be logged in the ceph-mgr log.'),
200209
]
201210

202211
COMMANDS = [
@@ -243,6 +252,7 @@ def __init__(self, *args, **kwargs):
243252

244253
self.stop_server = False
245254
self.serve_event = threading.Event()
255+
self.max_requests = cast(int, self.get_localized_module_option('max_requests', 500))
246256

247257

248258
def serve(self):
@@ -599,6 +609,16 @@ def submit_request(self, _request, **kwargs):
599609
with self.requests_lock:
600610
request = CommandsRequest(_request)
601611
self.requests.append(request)
612+
if len(self.requests) > self.max_requests:
613+
req_to_trim = 0
614+
for i, req in enumerate(self.requests):
615+
if req.is_finished():
616+
self.log.error("Trimmed one finished request due to exceeded maximum requests limit")
617+
req_to_trim = i
618+
break
619+
else:
620+
self.log.error("Trimmed the oldest unfinished request due to exceeded maximum requests limit")
621+
self.requests.pop(req_to_trim)
602622
if kwargs.get('wait', 0):
603623
while not request.is_finished():
604624
time.sleep(0.001)

0 commit comments

Comments
 (0)