blob: 5208cb2d14f34b14c0691e17e4fdac25c4821ed5 [file] [log] [blame]
/*
* 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;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.util.Arrays;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.apache.cassandra.SchemaLoader;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.db.rows.BTreeRow;
import org.apache.cassandra.db.rows.BufferCell;
import org.apache.cassandra.db.rows.Cell;
import org.apache.cassandra.db.rows.Cells;
import org.apache.cassandra.db.context.CounterContext;
import org.apache.cassandra.db.rows.Row;
import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.schema.KeyspaceParams;
import org.apache.cassandra.serializers.AsciiSerializer;
import org.apache.cassandra.utils.*;
import static org.junit.Assert.*;
import static org.apache.cassandra.db.context.CounterContext.ContextState;
public class CounterCellTest
{
private static final CounterContext cc = new CounterContext();
private static final int idLength;
private static final int clockLength;
private static final int countLength;
private static final int stepLength;
private static final String KEYSPACE1 = "CounterCacheTest";
private static final String COUNTER1 = "Counter1";
private static final String STANDARD1 = "Standard1";
@BeforeClass
public static void defineSchema() throws ConfigurationException
{
SchemaLoader.prepareServer();
SchemaLoader.createKeyspace(KEYSPACE1,
KeyspaceParams.simple(1),
SchemaLoader.standardCFMD(KEYSPACE1, STANDARD1),
SchemaLoader.counterCFMD(KEYSPACE1, COUNTER1));
}
@AfterClass
public static void cleanup()
{
SchemaLoader.cleanupSavedCaches();
}
static
{
idLength = CounterId.LENGTH;
clockLength = 8; // size of long
countLength = 8; // size of long
stepLength = idLength + clockLength + countLength;
}
@Test
public void testCreate()
{
ColumnFamilyStore cfs = Keyspace.open(KEYSPACE1).getColumnFamilyStore(COUNTER1);
long delta = 3L;
Cell cell = createLegacyCounterCell(cfs, ByteBufferUtil.bytes("val"), delta, 1);
assertEquals(delta, CounterContext.instance().total(cell.value()));
assertEquals(1, cell.value().getShort(0));
assertEquals(0, cell.value().getShort(2));
Assert.assertTrue(CounterId.wrap(cell.value(), 4).isLocalId());
assertEquals(1L, cell.value().getLong(4 + idLength));
assertEquals(delta, cell.value().getLong(4 + idLength + clockLength));
}
private Cell createLegacyCounterCell(ColumnFamilyStore cfs, ByteBuffer colName, long count, long ts)
{
ColumnDefinition cDef = cfs.metadata.getColumnDefinition(colName);
ByteBuffer val = CounterContext.instance().createLocal(count);
return BufferCell.live(cDef, ts, val);
}
private Cell createCounterCell(ColumnFamilyStore cfs, ByteBuffer colName, CounterId id, long count, long ts)
{
ColumnDefinition cDef = cfs.metadata.getColumnDefinition(colName);
ByteBuffer val = CounterContext.instance().createGlobal(id, ts, count);
return BufferCell.live(cDef, ts, val);
}
private Cell createCounterCellFromContext(ColumnFamilyStore cfs, ByteBuffer colName, ContextState context, long ts)
{
ColumnDefinition cDef = cfs.metadata.getColumnDefinition(colName);
return BufferCell.live(cDef, ts, context.context);
}
private Cell createDeleted(ColumnFamilyStore cfs, ByteBuffer colName, long ts, int localDeletionTime)
{
ColumnDefinition cDef = cfs.metadata.getColumnDefinition(colName);
return BufferCell.tombstone(cDef, ts, localDeletionTime);
}
@Test
public void testReconcile()
{
ColumnFamilyStore cfs = Keyspace.open(KEYSPACE1).getColumnFamilyStore(COUNTER1);
ByteBuffer col = ByteBufferUtil.bytes("val");
Cell left;
Cell right;
// both deleted, diff deletion time, same ts
left = createDeleted(cfs, col, 2, 5);
right = createDeleted(cfs, col, 2, 10);
assert Cells.reconcile(left, right, 10) == right;
// diff ts
right = createLegacyCounterCell(cfs, col, 1, 10);
assert Cells.reconcile(left, right, 10) == left;
// < tombstone
left = createDeleted(cfs, col, 6, 6);
right = createLegacyCounterCell(cfs, col, 1, 5);
assert Cells.reconcile(left, right, 10) == left;
// > tombstone
left = createDeleted(cfs, col, 1, 1);
right = createLegacyCounterCell(cfs, col, 1, 5);
assert Cells.reconcile(left, right, 10) == left;
// == tombstone
left = createDeleted(cfs, col, 8, 8);
right = createLegacyCounterCell(cfs, col, 1, 8);
assert Cells.reconcile(left, right, 10) == left;
// live + live
left = createLegacyCounterCell(cfs, col, 1, 2);
right = createLegacyCounterCell(cfs, col, 3, 5);
Cell reconciled = Cells.reconcile(left, right, 10);
assertEquals(CounterContext.instance().total(reconciled.value()), 4);
assertEquals(reconciled.timestamp(), 5L);
// Add, don't change TS
Cell addTen = createLegacyCounterCell(cfs, col, 10, 4);
reconciled = Cells.reconcile(reconciled, addTen, 10);
assertEquals(CounterContext.instance().total(reconciled.value()), 14);
assertEquals(reconciled.timestamp(), 5L);
// Add w/new TS
Cell addThree = createLegacyCounterCell(cfs, col, 3, 7);
reconciled = Cells.reconcile(reconciled, addThree, 10);
assertEquals(CounterContext.instance().total(reconciled.value()), 17);
assertEquals(reconciled.timestamp(), 7L);
// Confirm no deletion time
assert reconciled.localDeletionTime() == Integer.MAX_VALUE;
Cell deleted = createDeleted(cfs, col, 8, 8);
reconciled = Cells.reconcile(reconciled, deleted, 10);
assert reconciled.localDeletionTime() == 8;
}
@Test
public void testDiff()
{
ColumnFamilyStore cfs = Keyspace.open(KEYSPACE1).getColumnFamilyStore(COUNTER1);
ByteBuffer col = ByteBufferUtil.bytes("val");
Cell leftCell;
Cell rightCell;
// Equal count
leftCell = createLegacyCounterCell(cfs, col, 2, 2);
rightCell = createLegacyCounterCell(cfs, col, 2, 1);
assertEquals(CounterContext.Relationship.EQUAL, CounterContext.instance().diff(leftCell.value(), rightCell.value()));
// Non-equal count
leftCell = createLegacyCounterCell(cfs, col, 1, 2);
rightCell = createLegacyCounterCell(cfs, col, 2, 1);
assertEquals(CounterContext.Relationship.DISJOINT, CounterContext.instance().diff(leftCell.value(), rightCell.value()));
// timestamp
CounterId id = CounterId.generate();
leftCell = createCounterCell(cfs, col, id, 2, 2);
rightCell = createCounterCell(cfs, col, id, 2, 1);
assertEquals(CounterContext.Relationship.GREATER_THAN, CounterContext.instance().diff(leftCell.value(), rightCell.value()));
ContextState leftContext;
ContextState rightContext;
// Equal based on context w/shards etc
leftContext = ContextState.allocate(0, 0, 3);
leftContext.writeRemote(CounterId.fromInt(3), 3L, 0L);
leftContext.writeRemote(CounterId.fromInt(6), 2L, 0L);
leftContext.writeRemote(CounterId.fromInt(9), 1L, 0L);
rightContext = ContextState.wrap(ByteBufferUtil.clone(leftContext.context));
leftCell = createCounterCellFromContext(cfs, col, leftContext, 1);
rightCell = createCounterCellFromContext(cfs, col, rightContext, 1);
assertEquals(CounterContext.Relationship.EQUAL, CounterContext.instance().diff(leftCell.value(), rightCell.value()));
// greater than: left has superset of nodes (counts equal)
leftContext = ContextState.allocate(0, 0, 4);
leftContext.writeRemote(CounterId.fromInt(3), 3L, 0L);
leftContext.writeRemote(CounterId.fromInt(6), 2L, 0L);
leftContext.writeRemote(CounterId.fromInt(9), 1L, 0L);
leftContext.writeRemote(CounterId.fromInt(12), 0L, 0L);
rightContext = ContextState.allocate(0, 0, 3);
rightContext.writeRemote(CounterId.fromInt(3), 3L, 0L);
rightContext.writeRemote(CounterId.fromInt(6), 2L, 0L);
rightContext.writeRemote(CounterId.fromInt(9), 1L, 0L);
leftCell = createCounterCellFromContext(cfs, col, leftContext, 1);
rightCell = createCounterCellFromContext(cfs, col, rightContext, 1);
assertEquals(CounterContext.Relationship.GREATER_THAN, CounterContext.instance().diff(leftCell.value(), rightCell.value()));
assertEquals(CounterContext.Relationship.LESS_THAN, CounterContext.instance().diff(rightCell.value(), leftCell.value()));
// disjoint: right and left have disjoint node sets
leftContext = ContextState.allocate(0, 0, 3);
leftContext.writeRemote(CounterId.fromInt(3), 1L, 0L);
leftContext.writeRemote(CounterId.fromInt(4), 1L, 0L);
leftContext.writeRemote(CounterId.fromInt(9), 1L, 0L);
rightContext = ContextState.allocate(0, 0, 3);
rightContext.writeRemote(CounterId.fromInt(3), 1L, 0L);
rightContext.writeRemote(CounterId.fromInt(6), 1L, 0L);
rightContext.writeRemote(CounterId.fromInt(9), 1L, 0L);
leftCell = createCounterCellFromContext(cfs, col, leftContext, 1);
rightCell = createCounterCellFromContext(cfs, col, rightContext, 1);
assertEquals(CounterContext.Relationship.DISJOINT, CounterContext.instance().diff(leftCell.value(), rightCell.value()));
assertEquals(CounterContext.Relationship.DISJOINT, CounterContext.instance().diff(rightCell.value(), leftCell.value()));
}
@Test
public void testUpdateDigest() throws Exception
{
ColumnFamilyStore cfs = Keyspace.open(KEYSPACE1).getColumnFamilyStore(COUNTER1);
ByteBuffer col = ByteBufferUtil.bytes("val");
MessageDigest digest1 = MessageDigest.getInstance("md5");
MessageDigest digest2 = MessageDigest.getInstance("md5");
CounterContext.ContextState state = CounterContext.ContextState.allocate(0, 2, 2);
state.writeRemote(CounterId.fromInt(1), 4L, 4L);
state.writeLocal(CounterId.fromInt(2), 4L, 4L);
state.writeRemote(CounterId.fromInt(3), 4L, 4L);
state.writeLocal(CounterId.fromInt(4), 4L, 4L);
Cell original = createCounterCellFromContext(cfs, col, state, 5);
ColumnDefinition cDef = cfs.metadata.getColumnDefinition(col);
Cell cleared = BufferCell.live(cDef, 5, CounterContext.instance().clearAllLocal(state.context));
original.digest(digest1);
cleared.digest(digest2);
assert Arrays.equals(digest1.digest(), digest2.digest());
}
@Test
public void testDigestWithEmptyCells() throws Exception
{
// For DB-1881
ColumnFamilyStore cfs = Keyspace.open(KEYSPACE1).getColumnFamilyStore(COUNTER1);
ColumnDefinition emptyColDef = cfs.metadata.getColumnDefinition(ByteBufferUtil.bytes("val2"));
BufferCell emptyCell = BufferCell.live(emptyColDef, 0, ByteBuffer.allocate(0));
Row.Builder builder = BTreeRow.unsortedBuilder(0);
builder.newRow(Clustering.make(AsciiSerializer.instance.serialize("test")));
builder.addCell(emptyCell);
Row row = builder.build();
MessageDigest digest = MessageDigest.getInstance("md5");
row.digest(digest);
assertNotNull(digest.digest());
}
}