Check that the limit is never over 5000 in Loki queries

Loki has hard limit of 5000 rows it can return at once, and if the user
tries to bring more, it will just deny the request. Instead of
delegating the request denial to Loki, we add the proper checks before
the queries are done, so we can properly inform the user that more than
5000 rows is not acceptable.

Change-Id: I9b98bb7d0c51724b1a63e2ecbf01ffe4a4f1c8e6
Signed-off-by: Juan Larriba <jlarriba@redhat.com>
This commit is contained in:
Juan Larriba
2025-12-03 15:10:49 +01:00
parent 4ecb755fc5
commit 33e29d33f6
3 changed files with 51 additions and 0 deletions

View File

@@ -17,6 +17,7 @@ import json
from oslo_config import cfg
from oslo_log import log as oslo_logging
from werkzeug import exceptions as http_exceptions
from cloudkitty import dataframe
from cloudkitty.storage import v2 as v2_storage
@@ -161,6 +162,10 @@ class LokiStorage(v2_storage.BaseStorage):
filters=None,
metric_types=None,
offset=0, limit=1000, paginate=True):
if limit > 5000:
raise http_exceptions.BadRequest(
f"Limit {limit} exceeds maximum allowed limit of 5000 for "
f"Loki storage. Please reduce the limit parameter.")
begin, end = self._local_to_utc(begin or tzutils.get_month_start(),
end or tzutils.get_next_month())
total, logs = self._conn.retrieve(
@@ -195,6 +200,10 @@ class LokiStorage(v2_storage.BaseStorage):
def total(self, groupby=None, begin=None, end=None, metric_types=None,
filters=None, custom_fields=None, offset=0, limit=1000,
paginate=False):
if limit > 5000:
raise http_exceptions.BadRequest(
f"Limit {limit} exceeds maximum allowed limit of 5000 for "
f"Loki storage. Please reduce the limit parameter.")
begin, end = self._local_to_utc(begin or tzutils.get_month_start(),
end or tzutils.get_next_month())

View File

@@ -422,3 +422,44 @@ class StorageUnitTest(TestCase):
StorageUnitTest.generate_scenarios()
class LokiStorageLimitTest(TestCase):
"""Test Loki-specific limit validation"""
@mock.patch(_LOKI_CLIENT_PATH, new=loki_utils.FakeLokiClient)
@mock.patch('cloudkitty.utils.load_conf', new=test_utils.load_conf)
def setUp(self):
super(LokiStorageLimitTest, self).setUp()
self.conf.set_override('backend', 'loki', 'storage')
self.conf.set_override('version', '2', 'storage')
self.storage = storage.get_storage(conf=test_utils.load_conf())
self.storage.init()
def test_retrieve_with_limit_over_5000_raises_exception(self):
from werkzeug import exceptions as http_exceptions
self.assertRaisesRegex(
http_exceptions.BadRequest,
r'Limit 5001 exceeds maximum allowed limit of 5000',
self.storage.retrieve,
limit=5001
)
def test_retrieve_with_limit_5000_succeeds(self):
# This should not raise an exception
result = self.storage.retrieve(limit=5000)
self.assertIsNotNone(result)
def test_total_with_limit_over_5000_raises_exception(self):
from werkzeug import exceptions as http_exceptions
self.assertRaisesRegex(
http_exceptions.BadRequest,
r'Limit 5001 exceeds maximum allowed limit of 5000',
self.storage.total,
limit=5001
)
def test_total_with_limit_5000_succeeds(self):
# This should not raise an exception
result = self.storage.total(limit=5000)
self.assertIsNotNone(result)

View File

@@ -32,6 +32,7 @@ influxdb>=5.3.1 # MIT
influxdb-client>=1.36.0 # MIT
Flask>=2.0.0 # BSD
Flask-RESTful>=0.3.9 # BSD
Werkzeug>=2.0.0 # BSD
cotyledon>=1.7.3 # Apache-2.0
futurist>=2.3.0 # Apache-2.0
datetimerange>=0.6.1 # MIT