From 0c1eabc364999f02d42978c3dbac961f96eb893a Mon Sep 17 00:00:00 2001 From: Pedro Henrique Date: Mon, 17 Oct 2022 13:30:43 -0300 Subject: [PATCH] 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 --- cloudkitty/api/v2/task/reprocess.py | 33 +++++++++++ .../tests/api/v2/task/test_reprocess.py | 55 ++++++++++++++++++- ...mpatible-timewindows-5a44802f20bce4f2.yaml | 6 ++ 3 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/add-new-validation-to-not-allow-reprocessing-with-incompatible-timewindows-5a44802f20bce4f2.yaml diff --git a/cloudkitty/api/v2/task/reprocess.py b/cloudkitty/api/v2/task/reprocess.py index e132d882..197bb298 100644 --- a/cloudkitty/api/v2/task/reprocess.py +++ b/cloudkitty/api/v2/task/reprocess.py @@ -11,6 +11,7 @@ # License for the specific language governing permissions and limitations # under the License. # +from datetime import timedelta from datetimerange import DateTimeRange import flask from oslo_log import log @@ -24,6 +25,7 @@ from cloudkitty import storage_state from cloudkitty.storage_state.models import ReprocessingScheduler from cloudkitty.utils import tz as tzutils from cloudkitty.utils import validation as validation_utils +from oslo_config import cfg LOG = log.getLogger(__name__) @@ -91,6 +93,29 @@ class ReprocessSchedulerPostApi(base.BaseResource): 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 def validate_inputs( end_reprocess_time, reason, scope_ids, start_reprocess_time): @@ -107,6 +132,14 @@ class ReprocessSchedulerPostApi(base.BaseResource): "start reprocessing timestamp [%s]." % (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 def validate_scope_ids(scope_ids): option_all_selected = False diff --git a/cloudkitty/tests/api/v2/task/test_reprocess.py b/cloudkitty/tests/api/v2/task/test_reprocess.py index 29ae28f1..03946c30 100644 --- a/cloudkitty/tests/api/v2/task/test_reprocess.py +++ b/cloudkitty/tests/api/v2/task/test_reprocess.py @@ -31,9 +31,10 @@ class TestReprocessSchedulerPostApi(tests.TestCase): self.scope_ids = ["some-other-scope-id", "5e56cb64-4980-4466-9fce-d0133c0c221e"] - self.start_reprocess_time = tzutils.localized_now() - self.end_reprocess_time =\ - self.start_reprocess_time + datetime.timedelta(hours=1) + self.start_reprocess_time = self.endpoint.get_valid_period_date( + tzutils.localized_now()) + 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." @@ -100,6 +101,54 @@ class TestReprocessSchedulerPostApi(tests.TestCase): self.end_reprocess_time, self.reason, self.scope_ids, 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): all_scopes = self.generate_all_scopes_object() diff --git a/releasenotes/notes/add-new-validation-to-not-allow-reprocessing-with-incompatible-timewindows-5a44802f20bce4f2.yaml b/releasenotes/notes/add-new-validation-to-not-allow-reprocessing-with-incompatible-timewindows-5a44802f20bce4f2.yaml new file mode 100644 index 00000000..dcc4f0ae --- /dev/null +++ b/releasenotes/notes/add-new-validation-to-not-allow-reprocessing-with-incompatible-timewindows-5a44802f20bce4f2.yaml @@ -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.