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.