| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.cassandra.db.guardrails; |
| |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.function.BiConsumer; |
| import java.util.function.Function; |
| import java.util.function.ToIntFunction; |
| import java.util.function.ToLongFunction; |
| |
| import org.junit.Before; |
| import org.junit.Test; |
| |
| import org.apache.cassandra.config.DataStorageSpec; |
| import org.assertj.core.api.Assertions; |
| |
| import static java.lang.String.format; |
| import static org.apache.cassandra.config.DataStorageSpec.DataStorageUnit.BYTES; |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.fail; |
| |
| /** |
| * Utility class for testing a {@link Threshold} guardrail. |
| */ |
| public abstract class ThresholdTester extends GuardrailTester |
| { |
| private final long warnThreshold; |
| private final long failThreshold; |
| private final TriConsumer<Guardrails, Long, Long> setter; |
| private final ToLongFunction<Guardrails> warnGetter; |
| private final ToLongFunction<Guardrails> failGetter; |
| private final long maxValue; |
| private final Long disabledValue; |
| |
| protected ThresholdTester(int warnThreshold, |
| int failThreshold, |
| Threshold threshold, |
| TriConsumer<Guardrails, Integer, Integer> setter, |
| ToIntFunction<Guardrails> warnGetter, |
| ToIntFunction<Guardrails> failGetter) |
| { |
| super(threshold); |
| this.warnThreshold = warnThreshold; |
| this.failThreshold = failThreshold; |
| this.setter = (g, w, a) -> setter.accept(g, w.intValue(), a.intValue()); |
| this.warnGetter = g -> (long) warnGetter.applyAsInt(g); |
| this.failGetter = g -> (long) failGetter.applyAsInt(g); |
| maxValue = Integer.MAX_VALUE; |
| disabledValue = -1L; |
| } |
| |
| protected ThresholdTester(long warnThreshold, |
| long failThreshold, |
| Threshold threshold, |
| TriConsumer<Guardrails, Long, Long> setter, |
| ToLongFunction<Guardrails> warnGetter, |
| ToLongFunction<Guardrails> failGetter) |
| { |
| super(threshold); |
| this.warnThreshold = warnThreshold; |
| this.failThreshold = failThreshold; |
| this.setter = setter; |
| this.warnGetter = warnGetter; |
| this.failGetter = failGetter; |
| maxValue = Long.MAX_VALUE; |
| disabledValue = -1L; |
| } |
| |
| protected ThresholdTester(String warnThreshold, |
| String failThreshold, |
| Threshold threshold, |
| TriConsumer<Guardrails, String, String> setter, |
| Function<Guardrails, String> warnGetter, |
| Function<Guardrails, String> failGetter) |
| { |
| super(threshold); |
| this.warnThreshold = new DataStorageSpec.LongBytesBound(warnThreshold).toBytes(); |
| this.failThreshold = new DataStorageSpec.LongBytesBound(failThreshold).toBytes(); |
| this.setter = (g, w, a) -> setter.accept(g, w == null ? null : new DataStorageSpec.LongBytesBound(w, BYTES).toString(), a == null ? null : new DataStorageSpec.LongBytesBound(a, BYTES).toString()); |
| this.warnGetter = g -> new DataStorageSpec.LongBytesBound(warnGetter.apply(g)).toBytes(); |
| this.failGetter = g -> new DataStorageSpec.LongBytesBound(failGetter.apply(g)).toBytes(); |
| maxValue = Long.MAX_VALUE - 1; |
| disabledValue = null; |
| } |
| |
| protected long currentValue() |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| protected void assertCurrentValue(int count) |
| { |
| assertEquals(count, currentValue()); |
| } |
| |
| @Before |
| public void before() |
| { |
| setter.accept(guardrails(), warnThreshold, failThreshold); |
| } |
| |
| @Test |
| public void testConfigValidation() |
| { |
| assertNotNull(guardrail); |
| testValidationOfThresholdProperties(guardrail.name + "_warn_threshold", guardrail.name + "_fail_threshold"); |
| } |
| |
| protected void testValidationOfThresholdProperties(String warnName, String failName) |
| { |
| setter.accept(guardrails(), disabledValue, disabledValue); |
| |
| testValidationOfStrictlyPositiveProperty((g, a) -> setter.accept(g, disabledValue, a), failName); |
| testValidationOfStrictlyPositiveProperty((g, w) -> setter.accept(g, w, disabledValue), warnName); |
| |
| setter.accept(guardrails(), disabledValue, disabledValue); |
| Assertions.assertThatThrownBy(() -> setter.accept(guardrails(), 2L, 1L)) |
| .hasMessageContaining(guardrail.name + "_warn_threshold should be lower than the fail threshold"); |
| } |
| |
| protected void assertMaxThresholdValid(String query) throws Throwable |
| { |
| assertValid(query); |
| |
| long warnValue = warnGetter.applyAsLong(guardrails()); |
| long failValue = failGetter.applyAsLong(guardrails()); |
| long current = currentValue(); |
| |
| if (warnValue != disabledValue) |
| Assertions.assertThat(current) |
| .isLessThanOrEqualTo(warnValue); |
| |
| if (failValue != disabledValue) |
| Assertions.assertThat(current) |
| .isLessThanOrEqualTo(failValue); |
| } |
| |
| protected void assertMinThresholdValid(String query) throws Throwable |
| { |
| assertValid(query); |
| |
| long warnValue = warnGetter.applyAsLong(guardrails()); |
| long failValue = failGetter.applyAsLong(guardrails()); |
| long current = currentValue(); |
| |
| if (warnValue != disabledValue) |
| Assertions.assertThat(current) |
| .isGreaterThanOrEqualTo(warnValue); |
| |
| if (failValue != disabledValue) |
| Assertions.assertThat(current) |
| .isGreaterThanOrEqualTo(failValue); |
| } |
| |
| |
| protected void assertThresholdWarns(String query, String message) throws Throwable |
| { |
| assertThresholdWarns(query, message, message); |
| } |
| |
| protected void assertThresholdWarns(String query, String message, String redactedMessage) throws Throwable |
| { |
| assertThresholdWarns(query, Collections.singletonList(message), Collections.singletonList(redactedMessage)); |
| } |
| |
| protected void assertThresholdWarns(String query, List<String> messages) throws Throwable |
| { |
| assertThresholdWarns(query, messages, messages); |
| } |
| |
| protected void assertThresholdWarns(String query, List<String> messages, List<String> redactedMessages) throws Throwable |
| { |
| assertWarns(query, messages, redactedMessages); |
| |
| Assertions.assertThat(currentValue()) |
| .isGreaterThan(warnGetter.applyAsLong(guardrails())) |
| .isLessThanOrEqualTo(failGetter.applyAsLong(guardrails())); |
| } |
| |
| protected void assertThresholdFails(String query, String message) throws Throwable |
| { |
| assertThresholdFails(query, message, message); |
| } |
| |
| protected void assertThresholdFails(String query, String message, String redactedMessage) throws Throwable |
| { |
| assertThresholdFails(query, Collections.singletonList(message), Collections.singletonList(redactedMessage)); |
| } |
| |
| protected void assertThresholdFails(String query, List<String> messages) throws Throwable |
| { |
| assertThresholdFails(query, messages, messages); |
| } |
| |
| protected void assertThresholdFails(String query, List<String> messages, List<String> redactedMessages) throws Throwable |
| { |
| assertFails(query, messages, redactedMessages); |
| |
| Assertions.assertThat(currentValue()) |
| .isGreaterThanOrEqualTo(warnGetter.applyAsLong(guardrails())) |
| .isEqualTo(failGetter.applyAsLong(guardrails())); |
| } |
| |
| protected void assertInvalidPositiveProperty(BiConsumer<Guardrails, Long> setter, |
| long value, |
| long maxValue, |
| String name) |
| { |
| try |
| { |
| assertValidProperty(setter, value); |
| fail(format("Expected exception for guardrails.%s value: %d", name, value)); |
| } |
| catch (IllegalArgumentException e) |
| { |
| String expectedMessage = null; |
| |
| if (value > maxValue) |
| expectedMessage = format("Invalid value %d for %s: maximum allowed value is %d", |
| value, name, maxValue); |
| |
| if (value == 0 && value != disabledValue) |
| expectedMessage = format("Invalid value for %s: 0 is not allowed; if attempting to disable use %s", |
| name, disabledValue); |
| |
| if (value < 0 && disabledValue != null && value != disabledValue) |
| expectedMessage = format("Invalid value %d for %s: negative values are not " |
| + "allowed, outside of %s which disables the guardrail", |
| value, name, disabledValue); |
| |
| if (expectedMessage == null && value < 0) |
| expectedMessage = format("Invalid data storage: value must be non-negative"); |
| |
| assertEquals(format("Exception message '%s' does not contain '%s'", e.getMessage(), expectedMessage), |
| expectedMessage, e.getMessage()); |
| } |
| } |
| |
| private void assertInvalidStrictlyPositiveProperty(BiConsumer<Guardrails, Long> setter, long value, String name) |
| { |
| assertInvalidPositiveProperty(setter, value, maxValue, name); |
| } |
| |
| protected void assertInvalidPositiveIntProperty (BiConsumer<Guardrails, Integer> setter, int value, |
| int maxValue, |
| String name) |
| { |
| assertInvalidPositiveProperty((g, l) -> setter.accept(g, l.intValue()), (long) value, maxValue, name); |
| } |
| |
| protected void testValidationOfStrictlyPositiveProperty(BiConsumer<Guardrails, Long> setter, String name) |
| { |
| assertInvalidStrictlyPositiveProperty(setter, Integer.MIN_VALUE, name); |
| assertInvalidStrictlyPositiveProperty(setter, -2, name); |
| assertValidProperty(setter, disabledValue); |
| assertInvalidStrictlyPositiveProperty(setter, disabledValue == null ? -1 : 0, name); |
| assertValidProperty(setter, 1L); |
| assertValidProperty(setter, 2L); |
| assertValidProperty(setter, maxValue); |
| } |
| |
| @FunctionalInterface |
| public interface TriConsumer<T, U, V> |
| { |
| void accept(T t, U u, V v); |
| } |
| } |