/* * 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.schema.ColumnMetadata; 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.exceptions.ConfigurationException; import org.apache.cassandra.schema.KeyspaceParams; 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) { ColumnMetadata cDef = cfs.metadata().getColumn(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) { ColumnMetadata cDef = cfs.metadata().getColumn(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) { ColumnMetadata cDef = cfs.metadata().getColumn(colName); return BufferCell.live(cDef, ts, context.context); } private Cell createDeleted(ColumnFamilyStore cfs, ByteBuffer colName, long ts, int localDeletionTime) { ColumnMetadata cDef = cfs.metadata().getColumn(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); ColumnMetadata cDef = cfs.metadata().getColumn(col); Cell cleared = BufferCell.live(cDef, 5, CounterContext.instance().clearAllLocal(state.context)); CounterContext.instance().updateDigest(digest1, original.value()); CounterContext.instance().updateDigest(digest2, cleared.value()); assert Arrays.equals(digest1.digest(), digest2.digest()); } }