mirror of https://github.com/apache/cassandra
Add diagnostic events for guardrails
patch by Andrés de la Peña; reviewed by Berenguer Blasi and Stefan Miklosovic for CASSANDRA-17197 Co-authored-by: Andrés de la Peña <a.penya.garcia@gmail.com> Co-authored-by: Aleksandr Sorokoumov <aleksandr.sorokoumov@gmail.com>
This commit is contained in:
parent
2f84f492a2
commit
143a5e8b06
|
|
@ -1,4 +1,5 @@
|
|||
4.1
|
||||
* Add diagnostic events for guardrails (CASSANDRA-17197)
|
||||
* Increase cqlsh version (CASSANDRA-17432)
|
||||
* Update SUPPORTED_UPGRADE_PATHS to include 3.0 and 3.x to 4.1 paths and remove obsolete tests (CASSANDRA-17362)
|
||||
* Support DELETE in CQLSSTableWriter (CASSANDRA-14797)
|
||||
|
|
|
|||
|
|
@ -1583,7 +1583,7 @@ drop_compact_storage_enabled: false
|
|||
# abort_threshold_kb: 0
|
||||
|
||||
# Whether guardrails are enabled or not. Guardrails are disabled by default.
|
||||
# guardrails_enabled: true
|
||||
# guardrails_enabled: false
|
||||
# Guardrail to warn or fail when creating more user keyspaces than threshold.
|
||||
# The two thresholds default to -1 to disable.
|
||||
# keyspaces_warn_threshold: -1
|
||||
|
|
|
|||
|
|
@ -513,7 +513,7 @@ public abstract class Lists
|
|||
// Guardrails about collection size are only checked for the added elements without considering
|
||||
// already existent elements. This is done so to avoid read-before-write, having additional checks
|
||||
// during SSTable write.
|
||||
Guardrails.itemsPerCollection.guard(elements.size(), column.name.toString(), params.clientState);
|
||||
Guardrails.itemsPerCollection.guard(elements.size(), column.name.toString(), false, params.clientState);
|
||||
|
||||
int dataSize = 0;
|
||||
for (ByteBuffer buffer : elements)
|
||||
|
|
@ -522,13 +522,13 @@ public abstract class Lists
|
|||
Cell<?> cell = params.addCell(column, CellPath.create(uuid), buffer);
|
||||
dataSize += cell.dataSize();
|
||||
}
|
||||
Guardrails.collectionSize.guard(dataSize, column.name.toString(), params.clientState);
|
||||
Guardrails.collectionSize.guard(dataSize, column.name.toString(), false, params.clientState);
|
||||
}
|
||||
else
|
||||
{
|
||||
Guardrails.itemsPerCollection.guard(elements.size(), column.name.toString(), params.clientState);
|
||||
Guardrails.itemsPerCollection.guard(elements.size(), column.name.toString(), false, params.clientState);
|
||||
Cell<?> cell = params.addCell(column, value.get(ProtocolVersion.CURRENT));
|
||||
Guardrails.collectionSize.guard(cell.dataSize(), column.name.toString(), params.clientState);
|
||||
Guardrails.collectionSize.guard(cell.dataSize(), column.name.toString(), false, params.clientState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -441,7 +441,7 @@ public abstract class Maps
|
|||
// Guardrails about collection size are only checked for the added elements without considering
|
||||
// already existent elements. This is done so to avoid read-before-write, having additional checks
|
||||
// during SSTable write.
|
||||
Guardrails.itemsPerCollection.guard(elements.size(), column.name.toString(), params.clientState);
|
||||
Guardrails.itemsPerCollection.guard(elements.size(), column.name.toString(), false, params.clientState);
|
||||
|
||||
int dataSize = 0;
|
||||
for (Map.Entry<ByteBuffer, ByteBuffer> entry : elements.entrySet())
|
||||
|
|
@ -449,13 +449,13 @@ public abstract class Maps
|
|||
Cell<?> cell = params.addCell(column, CellPath.create(entry.getKey()), entry.getValue());
|
||||
dataSize += cell.dataSize();
|
||||
}
|
||||
Guardrails.collectionSize.guard(dataSize, column.name.toString(), params.clientState);
|
||||
Guardrails.collectionSize.guard(dataSize, column.name.toString(), false, params.clientState);
|
||||
}
|
||||
else
|
||||
{
|
||||
Guardrails.itemsPerCollection.guard(elements.size(), column.name.toString(), params.clientState);
|
||||
Guardrails.itemsPerCollection.guard(elements.size(), column.name.toString(), false, params.clientState);
|
||||
Cell<?> cell = params.addCell(column, value.get(ProtocolVersion.CURRENT));
|
||||
Guardrails.collectionSize.guard(cell.dataSize(), column.name.toString(), params.clientState);
|
||||
Guardrails.collectionSize.guard(cell.dataSize(), column.name.toString(), false, params.clientState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -368,7 +368,7 @@ public abstract class Sets
|
|||
// Guardrails about collection size are only checked for the added elements without considering
|
||||
// already existent elements. This is done so to avoid read-before-write, having additional checks
|
||||
// during SSTable write.
|
||||
Guardrails.itemsPerCollection.guard(elements.size(), column.name.toString(), params.clientState);
|
||||
Guardrails.itemsPerCollection.guard(elements.size(), column.name.toString(), false, params.clientState);
|
||||
|
||||
int dataSize = 0;
|
||||
for (ByteBuffer bb : elements)
|
||||
|
|
@ -379,13 +379,13 @@ public abstract class Sets
|
|||
Cell<?> cell = params.addCell(column, CellPath.create(bb), ByteBufferUtil.EMPTY_BYTE_BUFFER);
|
||||
dataSize += cell.dataSize();
|
||||
}
|
||||
Guardrails.collectionSize.guard(dataSize, column.name.toString(), params.clientState);
|
||||
Guardrails.collectionSize.guard(dataSize, column.name.toString(), false, params.clientState);
|
||||
}
|
||||
else
|
||||
{
|
||||
Guardrails.itemsPerCollection.guard(elements.size(), column.name.toString(), params.clientState);
|
||||
Guardrails.itemsPerCollection.guard(elements.size(), column.name.toString(), false, params.clientState);
|
||||
Cell<?> cell = params.addCell(column, value.get(ProtocolVersion.CURRENT));
|
||||
Guardrails.collectionSize.guard(cell.dataSize(), column.name.toString(), params.clientState);
|
||||
Guardrails.collectionSize.guard(cell.dataSize(), column.name.toString(), false, params.clientState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ final class ClusteringColumnRestrictions extends RestrictionSetWrapper
|
|||
r.appendTo(builder, options);
|
||||
|
||||
if (hasIN() && Guardrails.inSelectCartesianProduct.enabled(state))
|
||||
Guardrails.inSelectCartesianProduct.guard(builder.buildSize(), "clustering key", state);
|
||||
Guardrails.inSelectCartesianProduct.guard(builder.buildSize(), "clustering key", false, state);
|
||||
|
||||
if (builder.hasMissingElements())
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ final class PartitionKeySingleRestrictionSet extends RestrictionSetWrapper imple
|
|||
r.appendTo(builder, options);
|
||||
|
||||
if (hasIN() && Guardrails.inSelectCartesianProduct.enabled(state))
|
||||
Guardrails.inSelectCartesianProduct.guard(builder.buildSize(), "partition key", state);
|
||||
Guardrails.inSelectCartesianProduct.guard(builder.buildSize(), "partition key", false, state);
|
||||
|
||||
if (builder.hasMissingElements())
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -394,7 +394,7 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement
|
|||
int userLimit,
|
||||
long queryStartNanoTime) throws RequestValidationException, RequestExecutionException
|
||||
{
|
||||
Guardrails.pageSize.guard(pageSize, table(), state.getClientState());
|
||||
Guardrails.pageSize.guard(pageSize, table(), false, state.getClientState());
|
||||
|
||||
if (aggregationSpec != null)
|
||||
{
|
||||
|
|
@ -579,7 +579,7 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement
|
|||
|
||||
if (restrictions.keyIsInRelation())
|
||||
{
|
||||
Guardrails.partitionKeysInSelect.guard(keys.size(), table.name, state);
|
||||
Guardrails.partitionKeysInSelect.guard(keys.size(), table.name, false, state);
|
||||
}
|
||||
|
||||
ClusteringIndexFilter filter = makeClusteringIndexFilter(options, state, columnFilter);
|
||||
|
|
|
|||
|
|
@ -187,7 +187,7 @@ public abstract class AlterTableStatement extends AlterSchemaStatement
|
|||
Views.Builder viewsBuilder = keyspace.views.unbuild();
|
||||
newColumns.forEach(c -> addColumn(keyspace, table, c, tableBuilder, viewsBuilder));
|
||||
|
||||
Guardrails.columnsPerTable.guard(tableBuilder.numColumns(), tableName, state);
|
||||
Guardrails.columnsPerTable.guard(tableBuilder.numColumns(), tableName, false, state);
|
||||
|
||||
TableMetadata tableMetadata = tableBuilder.build();
|
||||
tableMetadata.validate();
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ public final class CreateIndexStatement extends AlterSchemaStatement
|
|||
Strings.isNullOrEmpty(indexName)
|
||||
? String.format("on table %s", table.name)
|
||||
: String.format("%s on table %s", indexName, table.name),
|
||||
false,
|
||||
state);
|
||||
|
||||
List<IndexTarget> indexTargets = Lists.newArrayList(transform(rawIndexTargets, t -> t.prepare(table)));
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ public final class CreateKeyspaceStatement extends AlterSchemaStatement
|
|||
super.validate(state);
|
||||
|
||||
// Guardrail on number of keyspaces
|
||||
Guardrails.keyspaces.guard(Schema.instance.getUserKeyspaces().size() + 1, keyspaceName, state);
|
||||
Guardrails.keyspaces.guard(Schema.instance.getUserKeyspaces().size() + 1, keyspaceName, false, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ public final class CreateTableStatement extends AlterSchemaStatement
|
|||
Guardrails.tableProperties.guard(attrs.updatedProperties(), attrs::removeProperty, state);
|
||||
|
||||
// Guardrail on columns per table
|
||||
Guardrails.columnsPerTable.guard(rawColumns.size(), tableName, state);
|
||||
Guardrails.columnsPerTable.guard(rawColumns.size(), tableName, false, state);
|
||||
|
||||
// Guardrail on number of tables
|
||||
if (Guardrails.tables.enabled(state))
|
||||
|
|
@ -144,7 +144,7 @@ public final class CreateTableStatement extends AlterSchemaStatement
|
|||
.map(Keyspace::open)
|
||||
.mapToInt(keyspace -> keyspace.getColumnFamilyStores().size())
|
||||
.sum();
|
||||
Guardrails.tables.guard(totalUserTables + 1, tableName, state);
|
||||
Guardrails.tables.guard(totalUserTables + 1, tableName, false, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -157,6 +157,7 @@ public final class CreateViewStatement extends AlterSchemaStatement
|
|||
Iterable<ViewMetadata> tableViews = keyspace.views.forTable(table.id);
|
||||
Guardrails.materializedViewsPerTable.guard(Iterables.size(tableViews) + 1,
|
||||
String.format("%s on table %s", viewName, table.name),
|
||||
false,
|
||||
state);
|
||||
|
||||
if (table.params.gcGraceSeconds == 0)
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ public abstract class Guardrail
|
|||
{
|
||||
protected static final NoSpamLogger logger = NoSpamLogger.getLogger(LoggerFactory.getLogger(Guardrail.class),
|
||||
10, TimeUnit.MINUTES);
|
||||
protected static final String REDACTED = "<redacted>";
|
||||
|
||||
/** A name identifying the guardrail (mainly for shipping with diagnostic events). */
|
||||
public final String name;
|
||||
|
|
@ -68,6 +69,11 @@ public abstract class Guardrail
|
|||
}
|
||||
|
||||
protected void warn(String message)
|
||||
{
|
||||
warn(message, message);
|
||||
}
|
||||
|
||||
protected void warn(String message, String redactedMessage)
|
||||
{
|
||||
message = decorateMessage(message);
|
||||
|
||||
|
|
@ -77,9 +83,15 @@ public abstract class Guardrail
|
|||
ClientWarn.instance.warn(message);
|
||||
// Similarly, tracing will also ignore the message if we're not running tracing on the current thread.
|
||||
Tracing.trace(message);
|
||||
GuardrailsDiagnostics.warned(name, decorateMessage(redactedMessage));
|
||||
}
|
||||
|
||||
protected void fail(String message, @Nullable ClientState state)
|
||||
{
|
||||
fail(message, message, state);
|
||||
}
|
||||
|
||||
protected void fail(String message, String redactedMessage, @Nullable ClientState state)
|
||||
{
|
||||
message = decorateMessage(message);
|
||||
|
||||
|
|
@ -89,6 +101,7 @@ public abstract class Guardrail
|
|||
ClientWarn.instance.warn(message);
|
||||
// Similarly, tracing will also ignore the message if we're not running tracing on the current thread.
|
||||
Tracing.trace(message);
|
||||
GuardrailsDiagnostics.failed(name, decorateMessage(redactedMessage));
|
||||
|
||||
if (state != null)
|
||||
throw new GuardrailViolatedException(message);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* 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.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.apache.cassandra.diag.DiagnosticEvent;
|
||||
|
||||
/**
|
||||
* {@link DiagnosticEvent} implementation for guardrail activation events.
|
||||
*/
|
||||
final class GuardrailEvent extends DiagnosticEvent
|
||||
{
|
||||
enum GuardrailEventType
|
||||
{
|
||||
WARNED, FAILED
|
||||
}
|
||||
|
||||
/** The type of activation, which is a warning or a failure. */
|
||||
private final GuardrailEventType type;
|
||||
|
||||
/** The name that identifies the activated guardrail. */
|
||||
private final String name;
|
||||
|
||||
/** The warn/fail message emitted by the activated guardrail. */
|
||||
private final String message;
|
||||
|
||||
/**
|
||||
* Creates new guardrail activation event.
|
||||
*
|
||||
* @param type The type of activation, which is warning or a failure.
|
||||
* @param name The name that identifies the activated guardrail.
|
||||
* @param message The warn/fail message emitted by the activated guardrail.
|
||||
*/
|
||||
GuardrailEvent(GuardrailEventType type, String name, String message)
|
||||
{
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enum<GuardrailEventType> getType()
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HashMap<String, Serializable> toMap()
|
||||
{
|
||||
HashMap<String, Serializable> ret = new HashMap<>();
|
||||
ret.put("name", name);
|
||||
ret.put("message", message);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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 org.apache.cassandra.db.guardrails.GuardrailEvent.GuardrailEventType;
|
||||
import org.apache.cassandra.diag.DiagnosticEventService;
|
||||
|
||||
/**
|
||||
* Utility methods for {@link GuardrailEvent} activities.
|
||||
*/
|
||||
final class GuardrailsDiagnostics
|
||||
{
|
||||
private static final DiagnosticEventService service = DiagnosticEventService.instance();
|
||||
|
||||
private GuardrailsDiagnostics()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new diagnostic event for the activation of the soft/warn limit of a guardrail.
|
||||
*
|
||||
* @param name The name that identifies the activated guardrail.
|
||||
* @param message The warning message emitted by the activated guardrail.
|
||||
*/
|
||||
static void warned(String name, String message)
|
||||
{
|
||||
if (isEnabled(GuardrailEventType.WARNED))
|
||||
service.publish(new GuardrailEvent(GuardrailEventType.WARNED, name, message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new diagnostic event for the activation of the hard/fail limit of a guardrail.
|
||||
*
|
||||
* @param name The name that identifies the activated guardrail.
|
||||
* @param message The failure message emitted by the activated guardrail.
|
||||
*/
|
||||
static void failed(String name, String message)
|
||||
{
|
||||
if (isEnabled(GuardrailEventType.FAILED))
|
||||
service.publish(new GuardrailEvent(GuardrailEventType.FAILED, name, message));
|
||||
}
|
||||
|
||||
private static boolean isEnabled(GuardrailEventType type)
|
||||
{
|
||||
return service.isEnabled(GuardrailEvent.class, type);
|
||||
}
|
||||
}
|
||||
|
|
@ -64,6 +64,11 @@ public class Threshold extends Guardrail
|
|||
thresholdValue);
|
||||
}
|
||||
|
||||
private String redactedErrMsg(boolean isWarning, long value, long thresholdValue)
|
||||
{
|
||||
return errMsg(isWarning, REDACTED, value, thresholdValue);
|
||||
}
|
||||
|
||||
private long failValue(ClientState state)
|
||||
{
|
||||
long failValue = failThreshold.applyAsLong(state);
|
||||
|
|
@ -106,17 +111,16 @@ public class Threshold extends Guardrail
|
|||
/**
|
||||
* Apply the guardrail to the provided value, warning or failing if appropriate.
|
||||
*
|
||||
* @param value The value to check.
|
||||
* @param what A string describing what {@code value} is a value of. This is used in the error message if the
|
||||
* guardrail is triggered. For instance, say the guardrail guards the size of column values, then this
|
||||
* argument must describe which column of which row is triggering the guardrail for convenience.
|
||||
* @param state The client state, used to skip the check if the query is internal or is done by a superuser.
|
||||
* A {@code null} value means that the check should be done regardless of the query, although it won't
|
||||
* throw any exception if the failure threshold is exceeded. This is so because checks without an
|
||||
* associated client come from asynchronous processes such as compaction, and we don't want to
|
||||
* interrupt such processes.
|
||||
* @param value The value to check.
|
||||
* @param what A string describing what {@code value} is a value of. This is used in the error message
|
||||
* if the guardrail is triggered. For instance, say the guardrail guards the size of column
|
||||
* values, then this argument must describe which column of which row is triggering the
|
||||
* guardrail for convenience.
|
||||
* @param containsUserData whether the {@code what} contains user data that should be redacted on external systems.
|
||||
* @param state The client state, used to skip the check if the query is internal or is done by a superuser.
|
||||
* A {@code null} value means that the check should be done regardless of the query.
|
||||
*/
|
||||
public void guard(long value, String what, @Nullable ClientState state)
|
||||
public void guard(long value, String what, boolean containsUserData, @Nullable ClientState state)
|
||||
{
|
||||
if (!enabled(state))
|
||||
return;
|
||||
|
|
@ -124,23 +128,25 @@ public class Threshold extends Guardrail
|
|||
long failValue = failValue(state);
|
||||
if (value > failValue)
|
||||
{
|
||||
triggerFail(value, failValue, what, state);
|
||||
triggerFail(value, failValue, what, containsUserData, state);
|
||||
return;
|
||||
}
|
||||
|
||||
long warnValue = warnValue(state);
|
||||
if (value > warnValue)
|
||||
triggerWarn(value, warnValue, what);
|
||||
triggerWarn(value, warnValue, what, containsUserData);
|
||||
}
|
||||
|
||||
private void triggerFail(long value, long failValue, String what, ClientState state)
|
||||
private void triggerFail(long value, long failValue, String what, boolean containsUserData, ClientState state)
|
||||
{
|
||||
fail(errMsg(false, what, value, failValue), state);
|
||||
String fullMessage = errMsg(false, what, value, failValue);
|
||||
fail(fullMessage, containsUserData ? redactedErrMsg(false, value, failValue) : fullMessage, state);
|
||||
}
|
||||
|
||||
private void triggerWarn(long value, long warnValue, String what)
|
||||
private void triggerWarn(long value, long warnValue, String what, boolean containsUserData)
|
||||
{
|
||||
warn(errMsg(true, what, value, warnValue));
|
||||
String fullMessage = errMsg(true, what, value, warnValue);
|
||||
warn(fullMessage, containsUserData ? redactedErrMsg(true, value, warnValue) : fullMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -430,8 +430,8 @@ public abstract class SSTableWriter extends SSTable implements Transactional
|
|||
column.name.toString(),
|
||||
keyString,
|
||||
metadata);
|
||||
Guardrails.collectionSize.guard(cellsSize, msg, null);
|
||||
Guardrails.itemsPerCollection.guard(cellsCount, msg, null);
|
||||
Guardrails.collectionSize.guard(cellsSize, msg, true, null);
|
||||
Guardrails.itemsPerCollection.guard(cellsCount, msg, true, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import java.util.Collections;
|
|||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.cassandra.db.marshal.BytesType;
|
||||
|
|
@ -55,6 +56,14 @@ public class GuardrailCollectionSizeTest extends ThresholdTester
|
|||
Guardrails::getCollectionSizeFailThresholdInKiB);
|
||||
}
|
||||
|
||||
@After
|
||||
public void after()
|
||||
{
|
||||
// immediately drop the created table so its async cleanup doesn't interfere with the next tests
|
||||
if (currentTable() != null)
|
||||
dropTable("DROP TABLE %s");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetSize() throws Throwable
|
||||
{
|
||||
|
|
@ -206,6 +215,14 @@ public class GuardrailCollectionSizeTest extends ThresholdTester
|
|||
assertWarns("UPDATE %s SET v = v + ? WHERE k = 6", map(allocate(FAIL_THRESHOLD / 4 + 1), allocate(FAIL_THRESHOLD / 4)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String createTable(String query)
|
||||
{
|
||||
String table = super.createTable(query);
|
||||
disableCompaction();
|
||||
return table;
|
||||
}
|
||||
|
||||
private void assertValid(String query, ByteBuffer... values) throws Throwable
|
||||
{
|
||||
assertValid(execute(query, values));
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
package org.apache.cassandra.db.guardrails;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
|
@ -167,9 +168,9 @@ public class GuardrailInSelectCartesianProductTest extends ThresholdTester
|
|||
else if (keys > WARN_THRESHOLD)
|
||||
{
|
||||
if (clusterings > FAIL_THRESHOLD)
|
||||
assertFails(function, keysWarnMessage, clusteringsFailMessage);
|
||||
assertFails(function, Arrays.asList(keysWarnMessage, clusteringsFailMessage));
|
||||
else if (clusterings > WARN_THRESHOLD)
|
||||
assertWarns(function, keysWarnMessage, clusteringsWarnMessage);
|
||||
assertWarns(function, Arrays.asList(keysWarnMessage, clusteringsWarnMessage));
|
||||
else
|
||||
assertWarns(function, keysWarnMessage);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import java.util.stream.Collector;
|
|||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.cassandra.db.marshal.Int32Type;
|
||||
|
|
@ -52,6 +53,14 @@ public class GuardrailItemsPerCollectionTest extends ThresholdTester
|
|||
Guardrails::getItemsPerCollectionFailThreshold);
|
||||
}
|
||||
|
||||
@After
|
||||
public void after()
|
||||
{
|
||||
// immediately drop the created table so its async cleanup doesn't interfere with the next tests
|
||||
if (currentTable() != null)
|
||||
dropTable("DROP TABLE %s");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetSize() throws Throwable
|
||||
{
|
||||
|
|
@ -208,6 +217,14 @@ public class GuardrailItemsPerCollectionTest extends ThresholdTester
|
|||
assertWarns("UPDATE %s SET v = v + ? WHERE k = 4", map(1, FAIL_THRESHOLD + 1), FAIL_THRESHOLD);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String createTable(String query)
|
||||
{
|
||||
String table = super.createTable(query);
|
||||
disableCompaction();
|
||||
return table;
|
||||
}
|
||||
|
||||
private void assertValid(String query, ByteBuffer collection) throws Throwable
|
||||
{
|
||||
assertValid(execute(query, collection));
|
||||
|
|
|
|||
|
|
@ -49,6 +49,11 @@ public class GuardrailTablePropertiesTest extends GuardrailTester
|
|||
private static final String IGNORED_PROPERTY_NAME = "table_properties_ignored";
|
||||
private static final String DISALLOWED_PROPERTY_NAME = "table_properties_disallowed";
|
||||
|
||||
public GuardrailTablePropertiesTest()
|
||||
{
|
||||
super(Guardrails.tableProperties);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void before()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -18,11 +18,15 @@
|
|||
|
||||
package org.apache.cassandra.db.guardrails;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
|
@ -31,18 +35,22 @@ import java.util.stream.Collectors;
|
|||
import javax.annotation.Nullable;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
|
||||
import org.apache.cassandra.auth.AuthenticatedUser;
|
||||
import org.apache.cassandra.auth.CassandraRoleManager;
|
||||
import org.apache.cassandra.config.DatabaseDescriptor;
|
||||
import org.apache.cassandra.cql3.CQLStatement;
|
||||
import org.apache.cassandra.cql3.CQLTester;
|
||||
import org.apache.cassandra.cql3.QueryOptions;
|
||||
import org.apache.cassandra.cql3.QueryProcessor;
|
||||
import org.apache.cassandra.db.ConsistencyLevel;
|
||||
import org.apache.cassandra.db.guardrails.GuardrailEvent.GuardrailEventType;
|
||||
import org.apache.cassandra.db.view.View;
|
||||
import org.apache.cassandra.diag.DiagnosticEventService;
|
||||
import org.apache.cassandra.index.sasi.SASIIndex;
|
||||
import org.apache.cassandra.service.ClientState;
|
||||
import org.apache.cassandra.service.ClientWarn;
|
||||
|
|
@ -55,6 +63,7 @@ import org.assertj.core.api.Assertions;
|
|||
import static java.lang.String.format;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
|
|
@ -70,12 +79,13 @@ public abstract class GuardrailTester extends CQLTester
|
|||
|
||||
protected static ClientState systemClientState, userClientState, superClientState;
|
||||
|
||||
/**
|
||||
* The tested guardrail, if we are testing a specific one.
|
||||
*/
|
||||
/** The tested guardrail, if we are testing a specific one. */
|
||||
@Nullable
|
||||
protected final Guardrail guardrail;
|
||||
|
||||
/** A listener for emitted diagnostic events. */
|
||||
protected final Listener listener;
|
||||
|
||||
public GuardrailTester()
|
||||
{
|
||||
this(null);
|
||||
|
|
@ -84,6 +94,7 @@ public abstract class GuardrailTester extends CQLTester
|
|||
public GuardrailTester(@Nullable Guardrail guardrail)
|
||||
{
|
||||
this.guardrail = guardrail;
|
||||
this.listener = new Listener();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
|
|
@ -93,6 +104,7 @@ public abstract class GuardrailTester extends CQLTester
|
|||
requireAuthentication();
|
||||
requireNetwork();
|
||||
guardrails().setEnabled(true);
|
||||
DatabaseDescriptor.setDiagnosticEventsEnabled(true);
|
||||
|
||||
systemClientState = ClientState.forInternalCalls();
|
||||
userClientState = ClientState.forExternalCalls(InetSocketAddress.createUnresolved("127.0.0.1", 123));
|
||||
|
|
@ -115,6 +127,14 @@ public abstract class GuardrailTester extends CQLTester
|
|||
execute(userClientState, useKeyspaceQuery);
|
||||
execute(systemClientState, useKeyspaceQuery);
|
||||
execute(superClientState, useKeyspaceQuery);
|
||||
|
||||
DiagnosticEventService.instance().subscribe(GuardrailEvent.class, listener);
|
||||
}
|
||||
|
||||
@After
|
||||
public void afterGuardrailTest() throws Throwable
|
||||
{
|
||||
DiagnosticEventService.instance().unsubscribe(listener);
|
||||
}
|
||||
|
||||
static Guardrails guardrails()
|
||||
|
|
@ -176,6 +196,8 @@ public abstract class GuardrailTester extends CQLTester
|
|||
{
|
||||
function.apply();
|
||||
assertEmptyWarnings();
|
||||
listener.assertNotWarned();
|
||||
listener.assertNotFailed();
|
||||
}
|
||||
catch (GuardrailViolatedException e)
|
||||
{
|
||||
|
|
@ -184,6 +206,7 @@ public abstract class GuardrailTester extends CQLTester
|
|||
finally
|
||||
{
|
||||
ClientWarn.instance.resetWarnings();
|
||||
listener.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -192,7 +215,42 @@ public abstract class GuardrailTester extends CQLTester
|
|||
assertValid(() -> execute(userClientState, query));
|
||||
}
|
||||
|
||||
protected void assertWarns(CheckedFunction function, String... messages) throws Throwable
|
||||
protected void assertWarns(String query, String message) throws Throwable
|
||||
{
|
||||
assertWarns(query, message, message);
|
||||
}
|
||||
|
||||
protected void assertWarns(String query, String message, String redactedMessage) throws Throwable
|
||||
{
|
||||
assertWarns(query, Collections.singletonList(message), Collections.singletonList(redactedMessage));
|
||||
}
|
||||
|
||||
protected void assertWarns(String query, List<String> messages) throws Throwable
|
||||
{
|
||||
assertWarns(() -> execute(userClientState, query), messages, messages);
|
||||
}
|
||||
|
||||
protected void assertWarns(String query, List<String> messages, List<String> redactedMessages) throws Throwable
|
||||
{
|
||||
assertWarns(() -> execute(userClientState, query), messages, redactedMessages);
|
||||
}
|
||||
|
||||
protected void assertWarns(CheckedFunction function, String message) throws Throwable
|
||||
{
|
||||
assertWarns(function, message, message);
|
||||
}
|
||||
|
||||
protected void assertWarns(CheckedFunction function, String message, String redactedMessage) throws Throwable
|
||||
{
|
||||
assertWarns(function, Collections.singletonList(message), Collections.singletonList(redactedMessage));
|
||||
}
|
||||
|
||||
protected void assertWarns(CheckedFunction function, List<String> messages) throws Throwable
|
||||
{
|
||||
assertWarns(function, messages, messages);
|
||||
}
|
||||
|
||||
protected void assertWarns(CheckedFunction function, List<String> messages, List<String> redactedMessages) throws Throwable
|
||||
{
|
||||
// We use client warnings to check we properly warn as this is the most convenient. Technically,
|
||||
// this doesn't validate we also log the warning, but that's probably fine ...
|
||||
|
|
@ -201,24 +259,67 @@ public abstract class GuardrailTester extends CQLTester
|
|||
{
|
||||
function.apply();
|
||||
assertWarnings(messages);
|
||||
listener.assertWarned(redactedMessages);
|
||||
listener.assertNotFailed();
|
||||
}
|
||||
finally
|
||||
{
|
||||
ClientWarn.instance.resetWarnings();
|
||||
listener.clear();
|
||||
}
|
||||
}
|
||||
|
||||
protected void assertWarns(String query, String... messages) throws Throwable
|
||||
protected void assertFails(String query, String message) throws Throwable
|
||||
{
|
||||
assertWarns(() -> execute(userClientState, query), messages);
|
||||
assertFails(query, message, message);
|
||||
}
|
||||
|
||||
protected void assertFails(CheckedFunction function, String... messages) throws Throwable
|
||||
protected void assertFails(String query, String message, String redactedMessage) throws Throwable
|
||||
{
|
||||
assertFails(function, true, messages);
|
||||
assertFails(query, Collections.singletonList(message), Collections.singletonList(redactedMessage));
|
||||
}
|
||||
|
||||
protected void assertFails(CheckedFunction function, boolean thrown, String... messages) throws Throwable
|
||||
protected void assertFails(String query, List<String> messages) throws Throwable
|
||||
{
|
||||
assertFails(query, messages, messages);
|
||||
}
|
||||
|
||||
protected void assertFails(String query, List<String> messages, List<String> redactedMessages) throws Throwable
|
||||
{
|
||||
assertFails(() -> execute(userClientState, query), messages, redactedMessages);
|
||||
}
|
||||
|
||||
protected void assertFails(CheckedFunction function, String message) throws Throwable
|
||||
{
|
||||
assertFails(function, message, message);
|
||||
}
|
||||
|
||||
protected void assertFails(CheckedFunction function, String message, String redactedMessage) throws Throwable
|
||||
{
|
||||
assertFails(function, true, message, redactedMessage);
|
||||
}
|
||||
|
||||
protected void assertFails(CheckedFunction function, boolean thrown, String message) throws Throwable
|
||||
{
|
||||
assertFails(function, thrown, message, message);
|
||||
}
|
||||
|
||||
protected void assertFails(CheckedFunction function, boolean thrown, String message, String redactedMessage) throws Throwable
|
||||
{
|
||||
assertFails(function, thrown, Collections.singletonList(message), Collections.singletonList(redactedMessage));
|
||||
}
|
||||
|
||||
protected void assertFails(CheckedFunction function, List<String> messages) throws Throwable
|
||||
{
|
||||
assertFails(function, messages, messages);
|
||||
}
|
||||
|
||||
protected void assertFails(CheckedFunction function, List<String> messages, List<String> redactedMessages) throws Throwable
|
||||
{
|
||||
assertFails(function, true, messages, redactedMessages);
|
||||
}
|
||||
|
||||
protected void assertFails(CheckedFunction function, boolean thrown, List<String> messages, List<String> redactedMessages) throws Throwable
|
||||
{
|
||||
ClientWarn.instance.captureWarnings();
|
||||
try
|
||||
|
|
@ -233,7 +334,7 @@ public abstract class GuardrailTester extends CQLTester
|
|||
assertTrue("Expect no exception thrown", thrown);
|
||||
|
||||
// the last message is the one raising the guardrail failure, the previous messages are warnings
|
||||
String failMessage = messages[messages.length - 1];
|
||||
String failMessage = messages.get(messages.size() - 1);
|
||||
|
||||
if (guardrail != null)
|
||||
{
|
||||
|
|
@ -246,16 +347,22 @@ public abstract class GuardrailTester extends CQLTester
|
|||
e.getMessage().contains(failMessage));
|
||||
|
||||
assertWarnings(messages);
|
||||
if (messages.size() > 1)
|
||||
listener.assertWarned(redactedMessages.subList(0, messages.size() - 1));
|
||||
else
|
||||
listener.assertNotWarned();
|
||||
listener.assertFailed(redactedMessages.get(messages.size() - 1));
|
||||
}
|
||||
finally
|
||||
{
|
||||
ClientWarn.instance.resetWarnings();
|
||||
listener.clear();
|
||||
}
|
||||
}
|
||||
|
||||
protected void assertFails(String query, String... messages) throws Throwable
|
||||
{
|
||||
assertFails(() -> execute(userClientState, query), messages);
|
||||
assertFails(() -> execute(userClientState, query), Arrays.asList(messages));
|
||||
}
|
||||
|
||||
protected void assertThrows(CheckedFunction function, Class<? extends Throwable> exception, String message)
|
||||
|
|
@ -275,20 +382,19 @@ public abstract class GuardrailTester extends CQLTester
|
|||
}
|
||||
}
|
||||
|
||||
private void assertWarnings(String... messages)
|
||||
private void assertWarnings(List<String> messages)
|
||||
{
|
||||
List<String> warnings = getWarnings();
|
||||
|
||||
assertFalse("Expected to warn, but no warning was received", warnings == null || warnings.isEmpty());
|
||||
assertEquals(format("Expected %d warnings but got %d: %s", messages.length, warnings.size(), warnings),
|
||||
messages.length,
|
||||
assertEquals(format("Expected %d warnings but got %d: %s", messages.size(), warnings.size(), warnings),
|
||||
messages.size(),
|
||||
warnings.size());
|
||||
|
||||
for (int i = 0; i < messages.length; i++)
|
||||
for (int i = 0; i < messages.size(); i++)
|
||||
{
|
||||
String message = messages.get(i);
|
||||
String warning = warnings.get(i);
|
||||
|
||||
String message = messages[i];
|
||||
if (guardrail != null)
|
||||
{
|
||||
String prefix = guardrail.decorateMessage("");
|
||||
|
|
@ -383,4 +489,84 @@ public abstract class GuardrailTester extends CQLTester
|
|||
{
|
||||
return String.join(",", (new TreeSet<>(ImmutableSet.copyOf((csv.split(","))))));
|
||||
}
|
||||
|
||||
/**
|
||||
* A listener for guardrails diagnostic events.
|
||||
*/
|
||||
public class Listener implements Consumer<GuardrailEvent>
|
||||
{
|
||||
private final List<String> warnings = new CopyOnWriteArrayList<>();
|
||||
private final List<String> failures = new CopyOnWriteArrayList<>();
|
||||
|
||||
@Override
|
||||
public void accept(GuardrailEvent event)
|
||||
{
|
||||
assertNotNull(event);
|
||||
Map<String, Serializable> map = event.toMap();
|
||||
|
||||
if (guardrail != null)
|
||||
assertEquals(guardrail.name, map.get("name"));
|
||||
|
||||
GuardrailEventType type = (GuardrailEventType) event.getType();
|
||||
String message = map.toString();
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case WARNED:
|
||||
warnings.add(message);
|
||||
break;
|
||||
case FAILED:
|
||||
failures.add(message);
|
||||
break;
|
||||
default:
|
||||
fail("Unexpected diagnostic event:" + type);
|
||||
}
|
||||
}
|
||||
|
||||
public void clear()
|
||||
{
|
||||
warnings.clear();
|
||||
failures.clear();
|
||||
}
|
||||
|
||||
public void assertNotWarned()
|
||||
{
|
||||
assertTrue(format("Expect no warning diagnostic events but got %s", warnings), warnings.isEmpty());
|
||||
}
|
||||
|
||||
public void assertWarned(List<String> messages)
|
||||
{
|
||||
assertFalse("Expected to emit warning diagnostic event, but no warning was emitted", warnings.isEmpty());
|
||||
assertEquals(format("Expected %d warning diagnostic events but got %d: %s)", messages.size(), warnings.size(), warnings),
|
||||
messages.size(), warnings.size());
|
||||
|
||||
for (int i = 0; i < messages.size(); i++)
|
||||
{
|
||||
String message = messages.get(i);
|
||||
String warning = warnings.get(i);
|
||||
assertTrue(format("Warning diagnostic event '%s' does not contain expected message '%s'", warning, message),
|
||||
warning.contains(message));
|
||||
}
|
||||
}
|
||||
|
||||
public void assertNotFailed()
|
||||
{
|
||||
assertTrue(format("Expect no failure diagnostic events but got %s", failures), failures.isEmpty());
|
||||
}
|
||||
|
||||
public void assertFailed(String... messages)
|
||||
{
|
||||
assertFalse("Expected to emit failure diagnostic event, but no failure was emitted", failures.isEmpty());
|
||||
assertEquals(format("Expected %d failure diagnostic events but got %d: %s)", messages.length, failures.size(), failures),
|
||||
messages.length, failures.size());
|
||||
|
||||
for (int i = 0; i < messages.length; i++)
|
||||
{
|
||||
String message = messages[i];
|
||||
String failure = failures.get(i);
|
||||
assertTrue(format("Failure diagnostic event '%s' does not contain expected message '%s'", failure, message),
|
||||
failure.contains(message));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,12 +42,19 @@ public class GuardrailsConfigProviderTest extends GuardrailTester
|
|||
(isWarn, what, v, t) -> format("%s: for %s, %s > %s",
|
||||
isWarn ? "Warning" : "Aborting", what, v, t));
|
||||
|
||||
assertValid(() -> guard.guard(5, "Z", userClientState));
|
||||
assertWarns(() -> guard.guard(25, "A", userClientState), "Warning: for A, 25 > 10");
|
||||
assertWarns(() -> guard.guard(100, "B", userClientState), "Warning: for B, 100 > 10");
|
||||
assertFails(() -> guard.guard(101, "X", userClientState), "Aborting: for X, 101 > 100");
|
||||
assertFails(() -> guard.guard(200, "Y", userClientState), "Aborting: for Y, 200 > 100");
|
||||
assertValid(() -> guard.guard(5, "Z", userClientState));
|
||||
assertValid(() -> guard.guard(5, "Z", false, userClientState));
|
||||
assertWarns(() -> guard.guard(25, "A", false, userClientState), "Warning: for A, 25 > 10");
|
||||
assertWarns(() -> guard.guard(100, "B", false, userClientState), "Warning: for B, 100 > 10");
|
||||
assertFails(() -> guard.guard(101, "X", false, userClientState), "Aborting: for X, 101 > 100");
|
||||
assertFails(() -> guard.guard(200, "Y", false, userClientState), "Aborting: for Y, 200 > 100");
|
||||
assertValid(() -> guard.guard(5, "Z", false, userClientState));
|
||||
|
||||
assertValid(() -> guard.guard(5, "Z", true, userClientState));
|
||||
assertWarns(() -> guard.guard(25, "A", true, userClientState), "Warning: for A, 25 > 10", "Warning: for <redacted>, 25 > 10");
|
||||
assertWarns(() -> guard.guard(100, "B", true, userClientState), "Warning: for B, 100 > 10", "Warning: for <redacted>, 100 > 10");
|
||||
assertFails(() -> guard.guard(101, "X", true, userClientState), "Aborting: for X, 101 > 100", "Aborting: for <redacted>, 101 > 100");
|
||||
assertFails(() -> guard.guard(200, "Y", true, userClientState), "Aborting: for Y, 200 > 100", "Aborting: for <redacted>, 200 > 100");
|
||||
assertValid(() -> guard.guard(5, "Z", true, userClientState));
|
||||
|
||||
Assertions.assertThatThrownBy(() -> GuardrailsConfigProvider.build("unexistent_class"))
|
||||
.isInstanceOf(ConfigurationException.class)
|
||||
|
|
|
|||
|
|
@ -50,17 +50,20 @@ public class GuardrailsTest extends GuardrailTester
|
|||
{
|
||||
assertFalse(guard.enabled(userClientState));
|
||||
|
||||
assertValid(() -> guard.guard(5, "Z", null));
|
||||
assertValid(() -> guard.guard(25, "A", userClientState));
|
||||
assertValid(() -> guard.guard(100, "B", userClientState));
|
||||
assertValid(() -> guard.guard(101, "X", userClientState));
|
||||
assertValid(() -> guard.guard(200, "Y", userClientState));
|
||||
for (boolean containsUserData : Arrays.asList(true, false))
|
||||
{
|
||||
assertValid(() -> guard.guard(5, "Z", containsUserData, null));
|
||||
assertValid(() -> guard.guard(25, "A", containsUserData, userClientState));
|
||||
assertValid(() -> guard.guard(100, "B", containsUserData, userClientState));
|
||||
assertValid(() -> guard.guard(101, "X", containsUserData, userClientState));
|
||||
assertValid(() -> guard.guard(200, "Y", containsUserData, userClientState));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testThreshold() throws Throwable
|
||||
{
|
||||
Threshold guard = new Threshold("x",
|
||||
Threshold guard = new Threshold("x",
|
||||
state -> 10,
|
||||
state -> 100,
|
||||
(isWarn, what, v, t) -> format("%s: for %s, %s > %s",
|
||||
|
|
@ -68,12 +71,19 @@ public class GuardrailsTest extends GuardrailTester
|
|||
|
||||
assertTrue(guard.enabled(userClientState));
|
||||
|
||||
assertValid(() -> guard.guard(5, "Z", userClientState));
|
||||
assertWarns(() -> guard.guard(25, "A", userClientState), "Warning: for A, 25 > 10");
|
||||
assertWarns(() -> guard.guard(100, "B", userClientState), "Warning: for B, 100 > 10");
|
||||
assertFails(() -> guard.guard(101, "X", userClientState), "Aborting: for X, 101 > 100");
|
||||
assertFails(() -> guard.guard(200, "Y", userClientState), "Aborting: for Y, 200 > 100");
|
||||
assertValid(() -> guard.guard(5, "Z", userClientState));
|
||||
assertValid(() -> guard.guard(5, "Z", false, userClientState));
|
||||
assertWarns(() -> guard.guard(25, "A", false, userClientState), "Warning: for A, 25 > 10");
|
||||
assertWarns(() -> guard.guard(100, "B", false, userClientState), "Warning: for B, 100 > 10");
|
||||
assertFails(() -> guard.guard(101, "X", false, userClientState), "Aborting: for X, 101 > 100");
|
||||
assertFails(() -> guard.guard(200, "Y", false, userClientState), "Aborting: for Y, 200 > 100");
|
||||
assertValid(() -> guard.guard(5, "Z", false, userClientState));
|
||||
|
||||
assertValid(() -> guard.guard(5, "Z", true, userClientState));
|
||||
assertWarns(() -> guard.guard(25, "A", true, userClientState), "Warning: for A, 25 > 10", "Warning: for <redacted>, 25 > 10");
|
||||
assertWarns(() -> guard.guard(100, "B", true, userClientState), "Warning: for B, 100 > 10", "Warning: for <redacted>, 100 > 10");
|
||||
assertFails(() -> guard.guard(101, "X", true, userClientState), "Aborting: for X, 101 > 100", "Aborting: for <redacted>, 101 > 100");
|
||||
assertFails(() -> guard.guard(200, "Y", true, userClientState), "Aborting: for Y, 200 > 100", "Aborting: for <redacted>, 200 > 100");
|
||||
assertValid(() -> guard.guard(5, "Z", true, userClientState));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -87,8 +97,11 @@ public class GuardrailsTest extends GuardrailTester
|
|||
|
||||
assertTrue(guard.enabled(userClientState));
|
||||
|
||||
assertValid(() -> guard.guard(5, "Z", userClientState));
|
||||
assertWarns(() -> guard.guard(11, "A", userClientState), "Warning: for A, 11 > 10");
|
||||
assertValid(() -> guard.guard(5, "Z", false, userClientState));
|
||||
assertWarns(() -> guard.guard(11, "A", false, userClientState), "Warning: for A, 11 > 10");
|
||||
|
||||
assertValid(() -> guard.guard(5, "Z", true, userClientState));
|
||||
assertWarns(() -> guard.guard(11, "A", true, userClientState), "Warning: for A, 11 > 10", "Warning: for <redacted>, 11 > 10");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -102,8 +115,11 @@ public class GuardrailsTest extends GuardrailTester
|
|||
|
||||
assertTrue(guard.enabled(userClientState));
|
||||
|
||||
assertValid(() -> guard.guard(5, "Z", userClientState));
|
||||
assertFails(() -> guard.guard(11, "A", userClientState), "Aborting: for A, 11 > 10");
|
||||
assertValid(() -> guard.guard(5, "Z", false, userClientState));
|
||||
assertFails(() -> guard.guard(11, "A", false, userClientState), "Aborting: for A, 11 > 10");
|
||||
|
||||
assertValid(() -> guard.guard(5, "Z", true, userClientState));
|
||||
assertFails(() -> guard.guard(11, "A", true, userClientState), "Aborting: for A, 11 > 10", "Aborting: for <redacted>, 11 > 10");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -116,23 +132,23 @@ public class GuardrailsTest extends GuardrailTester
|
|||
isWarn ? "Warning" : "Failure", what, v, t));
|
||||
|
||||
// value under both thresholds
|
||||
assertValid(() -> guard.guard(5, "x", null));
|
||||
assertValid(() -> guard.guard(5, "x", userClientState));
|
||||
assertValid(() -> guard.guard(5, "x", systemClientState));
|
||||
assertValid(() -> guard.guard(5, "x", superClientState));
|
||||
assertValid(() -> guard.guard(5, "x", false, null));
|
||||
assertValid(() -> guard.guard(5, "x", false, userClientState));
|
||||
assertValid(() -> guard.guard(5, "x", false, systemClientState));
|
||||
assertValid(() -> guard.guard(5, "x", false, superClientState));
|
||||
|
||||
// value over warning threshold
|
||||
assertWarns(() -> guard.guard(100, "y", null), "Warning: for y, 100 > 10");
|
||||
assertWarns(() -> guard.guard(100, "y", userClientState), "Warning: for y, 100 > 10");
|
||||
assertValid(() -> guard.guard(100, "y", systemClientState));
|
||||
assertValid(() -> guard.guard(100, "y", superClientState));
|
||||
assertWarns(() -> guard.guard(100, "y", false, null), "Warning: for y, 100 > 10");
|
||||
assertWarns(() -> guard.guard(100, "y", false, userClientState), "Warning: for y, 100 > 10");
|
||||
assertValid(() -> guard.guard(100, "y", false, systemClientState));
|
||||
assertValid(() -> guard.guard(100, "y", false, superClientState));
|
||||
|
||||
// value over fail threshold. An undefined user means that the check comes from a background process,
|
||||
// so we warn instead of failing to prevent interrupting that process.
|
||||
assertWarns(() -> guard.guard(101, "z", null), "Failure: for z, 101 > 100");
|
||||
assertFails(() -> guard.guard(101, "z", userClientState), "Failure: for z, 101 > 100");
|
||||
assertValid(() -> guard.guard(101, "z", systemClientState));
|
||||
assertValid(() -> guard.guard(101, "z", superClientState));
|
||||
// value over fail threshold. An undefined user means that the check comes from a background process, so we
|
||||
// still emit failure messages and events, but we don't throw an exception to prevent interrupting that process.
|
||||
assertFails(() -> guard.guard(101, "z", false, null), false, "Failure: for z, 101 > 100");
|
||||
assertFails(() -> guard.guard(101, "z", false, userClientState), "Failure: for z, 101 > 100");
|
||||
assertValid(() -> guard.guard(101, "z", false, systemClientState));
|
||||
assertValid(() -> guard.guard(101, "z", false, superClientState));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -164,7 +180,7 @@ public class GuardrailsTest extends GuardrailTester
|
|||
public void testValuesWarned() throws Throwable
|
||||
{
|
||||
// Using a sorted set below to ensure the order in the warning message checked below is not random
|
||||
Values<Integer> warned = new Values<>("x",
|
||||
Values<Integer> warned = new Values<>("x",
|
||||
state -> insertionOrderedSet(4, 6, 20),
|
||||
state -> Collections.emptySet(),
|
||||
state -> Collections.emptySet(),
|
||||
|
|
@ -184,7 +200,7 @@ public class GuardrailsTest extends GuardrailTester
|
|||
public void testValuesIgnored() throws Throwable
|
||||
{
|
||||
// Using a sorted set below to ensure the order in the error message checked below are not random
|
||||
Values<Integer> ignored = new Values<>("x",
|
||||
Values<Integer> ignored = new Values<>("x",
|
||||
state -> Collections.emptySet(),
|
||||
state -> insertionOrderedSet(4, 6, 20),
|
||||
state -> Collections.emptySet(),
|
||||
|
|
@ -218,7 +234,7 @@ public class GuardrailsTest extends GuardrailTester
|
|||
public void testValuesDisallowed() throws Throwable
|
||||
{
|
||||
// Using a sorted set below to ensure the order in the error message checked below are not random
|
||||
Values<Integer> disallowed = new Values<>("x",
|
||||
Values<Integer> disallowed = new Values<>("x",
|
||||
state -> Collections.emptySet(),
|
||||
state -> Collections.emptySet(),
|
||||
state -> insertionOrderedSet(4, 6, 20),
|
||||
|
|
@ -234,16 +250,16 @@ public class GuardrailsTest extends GuardrailTester
|
|||
assertValid(() -> disallowed.guard(set(200), action, userClientState));
|
||||
assertValid(() -> disallowed.guard(set(1, 2, 3), action, userClientState));
|
||||
|
||||
assertWarns(() -> disallowed.guard(set(4, 6), action, null),
|
||||
assertFails(() -> disallowed.guard(set(4, 6), action, null), false,
|
||||
"Provided values [4, 6] are not allowed for integer (disallowed values are: [4, 6, 20])");
|
||||
assertWarns(() -> disallowed.guard(set(4, 5, 6, 7), action, null),
|
||||
assertFails(() -> disallowed.guard(set(4, 5, 6, 7), action, null), false,
|
||||
"Provided values [4, 6] are not allowed for integer (disallowed values are: [4, 6, 20])");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValuesUsers() throws Throwable
|
||||
{
|
||||
Values<Integer> disallowed = new Values<>("x",
|
||||
Values<Integer> disallowed = new Values<>("x",
|
||||
state -> Collections.singleton(2),
|
||||
state -> Collections.singleton(3),
|
||||
state -> Collections.singleton(4),
|
||||
|
|
@ -271,7 +287,7 @@ public class GuardrailsTest extends GuardrailTester
|
|||
Assert.assertEquals(list(3, 3), triggeredOn);
|
||||
|
||||
message = "Provided values [4] are not allowed for integer (disallowed values are: [4])";
|
||||
assertWarns(() -> disallowed.guard(set(4), action, null), message);
|
||||
assertFails(() -> disallowed.guard(set(4), action, null), false, message);
|
||||
assertFails(() -> disallowed.guard(set(4), action, userClientState), message);
|
||||
assertValid(() -> disallowed.guard(set(4), action, systemClientState));
|
||||
assertValid(() -> disallowed.guard(set(4), action, superClientState));
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@
|
|||
|
||||
package org.apache.cassandra.db.guardrails;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.ToIntFunction;
|
||||
import java.util.function.ToLongFunction;
|
||||
|
|
@ -125,18 +127,48 @@ public abstract class ThresholdTester extends GuardrailTester
|
|||
.isLessThanOrEqualTo(failGetter.applyAsLong(guardrails()));
|
||||
}
|
||||
|
||||
protected void assertThresholdWarns(String query, String... messages) throws Throwable
|
||||
protected void assertThresholdWarns(String query, String message) throws Throwable
|
||||
{
|
||||
assertWarns(query, messages);
|
||||
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... messages) throws Throwable
|
||||
protected void assertThresholdFails(String query, String message) throws Throwable
|
||||
{
|
||||
assertFails(query, messages);
|
||||
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()))
|
||||
|
|
|
|||
Loading…
Reference in New Issue