Validates the period compatibility of reprocessing dates

Problem description
===================

The reprocess API is accepting time windows that are
not compatible with the configured collection period
which causes some reprocessings to be endless or
generating different values based on the time window
the user inputs.

Proposal
========

We propose to add a validation in the reprocess API
to deny users to schedule a reprocess using a not
compatible time window and suggest the nearest valid
time window that the user can schedule a reprocess.

Change-Id: I24745a612bbd4714a7793df1deced671c1d1c26a
This commit is contained in:
Pedro Henrique 2022-10-17 13:30:43 -03:00
parent 386f086a8b
commit 0c1eabc364
3 changed files with 91 additions and 3 deletions

View File

@ -11,6 +11,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
# #
from datetime import timedelta
from datetimerange import DateTimeRange from datetimerange import DateTimeRange
import flask import flask
from oslo_log import log from oslo_log import log
@ -24,6 +25,7 @@ from cloudkitty import storage_state
from cloudkitty.storage_state.models import ReprocessingScheduler from cloudkitty.storage_state.models import ReprocessingScheduler
from cloudkitty.utils import tz as tzutils from cloudkitty.utils import tz as tzutils
from cloudkitty.utils import validation as validation_utils from cloudkitty.utils import validation as validation_utils
from oslo_config import cfg
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@ -91,6 +93,29 @@ class ReprocessSchedulerPostApi(base.BaseResource):
return {}, 202 return {}, 202
@staticmethod
def get_date_period_overflow(date):
return int(date.timestamp() % cfg.CONF.collect.period)
@staticmethod
def get_valid_period_date(date):
return date - timedelta(
seconds=ReprocessSchedulerPostApi.get_date_period_overflow(date))
@staticmethod
def get_overflow_from_dates(start, end):
start_overflow = ReprocessSchedulerPostApi.get_date_period_overflow(
start)
end_overflow = ReprocessSchedulerPostApi.get_date_period_overflow(end)
if start_overflow or end_overflow:
valid_start = ReprocessSchedulerPostApi.get_valid_period_date(
start)
valid_end = ReprocessSchedulerPostApi.get_valid_period_date(end)
if valid_start == valid_end:
valid_end += timedelta(seconds=cfg.CONF.collect.period)
return [str(valid_start), str(valid_end)]
@staticmethod @staticmethod
def validate_inputs( def validate_inputs(
end_reprocess_time, reason, scope_ids, start_reprocess_time): end_reprocess_time, reason, scope_ids, start_reprocess_time):
@ -107,6 +132,14 @@ class ReprocessSchedulerPostApi(base.BaseResource):
"start reprocessing timestamp [%s]." "start reprocessing timestamp [%s]."
% (start_reprocess_time, end_reprocess_time)) % (start_reprocess_time, end_reprocess_time))
periods_overflows = ReprocessSchedulerPostApi.get_overflow_from_dates(
start_reprocess_time, end_reprocess_time)
if periods_overflows:
raise http_exceptions.BadRequest(
"The provided reprocess time window does not comply with "
"the configured collector period. A valid time window "
"near the provided one is %s" % periods_overflows)
@staticmethod @staticmethod
def validate_scope_ids(scope_ids): def validate_scope_ids(scope_ids):
option_all_selected = False option_all_selected = False

View File

@ -31,9 +31,10 @@ class TestReprocessSchedulerPostApi(tests.TestCase):
self.scope_ids = ["some-other-scope-id", self.scope_ids = ["some-other-scope-id",
"5e56cb64-4980-4466-9fce-d0133c0c221e"] "5e56cb64-4980-4466-9fce-d0133c0c221e"]
self.start_reprocess_time = tzutils.localized_now() self.start_reprocess_time = self.endpoint.get_valid_period_date(
self.end_reprocess_time =\ tzutils.localized_now())
self.start_reprocess_time + datetime.timedelta(hours=1) self.end_reprocess_time = self.endpoint.get_valid_period_date(
self.start_reprocess_time + datetime.timedelta(hours=1))
self.reason = "We are testing the reprocess API." self.reason = "We are testing the reprocess API."
@ -100,6 +101,54 @@ class TestReprocessSchedulerPostApi(tests.TestCase):
self.end_reprocess_time, self.reason, self.scope_ids, self.end_reprocess_time, self.reason, self.scope_ids,
self.start_reprocess_time) self.start_reprocess_time)
def test_validate_inputs_different_from_configured_period(self):
original_end_reprocess_time = self.end_reprocess_time
self.end_reprocess_time += datetime.timedelta(seconds=1)
expected_message = "400 Bad Request: The provided reprocess time " \
"window does not comply with the configured" \
" collector period. A valid time window near " \
"the provided one is ['%s', '%s']" % (
self.start_reprocess_time,
original_end_reprocess_time)
expected_message = re.escape(expected_message)
self.assertRaisesRegex(http_exceptions.BadRequest, expected_message,
self.endpoint.validate_inputs,
self.end_reprocess_time, self.reason,
self.scope_ids, self.start_reprocess_time)
self.end_reprocess_time = original_end_reprocess_time
self.endpoint.validate_inputs(
self.end_reprocess_time, self.reason, self.scope_ids,
self.start_reprocess_time)
def test_validate_time_window_smaller_than_configured_period(self):
start = datetime.datetime(year=2022, day=22, month=2, hour=10,
minute=10, tzinfo=tzutils._LOCAL_TZ)
end = datetime.datetime(year=2022, day=22, month=2, hour=10,
minute=20, tzinfo=tzutils._LOCAL_TZ)
expected_start = datetime.datetime(year=2022, day=22, month=2, hour=10,
tzinfo=tzutils._LOCAL_TZ)
expected_end = datetime.datetime(year=2022, day=22, month=2, hour=11,
tzinfo=tzutils._LOCAL_TZ)
expected_message = "400 Bad Request: The provided reprocess time " \
"window does not comply with the configured" \
" collector period. A valid time window near " \
"the provided one is ['%s', '%s']" % (
expected_start,
expected_end)
expected_message = re.escape(expected_message)
self.assertRaisesRegex(http_exceptions.BadRequest, expected_message,
self.endpoint.validate_inputs,
end, self.reason,
self.scope_ids, start)
def test_check_if_there_are_invalid_scopes(self): def test_check_if_there_are_invalid_scopes(self):
all_scopes = self.generate_all_scopes_object() all_scopes = self.generate_all_scopes_object()

View File

@ -0,0 +1,6 @@
---
fixes:
- |
Add a validation to not allow users to schedule reprocesses via
``POST`` request on ``/v2/task/reprocesses`` using a time window not
compatible with the configured ``period`` in the collector.