/**
* 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.io.IOException;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
import static org.junit.Assert.fail;
import org.apache.cassandra.db.context.CounterContext;
import org.apache.cassandra.SchemaLoader;
import org.apache.cassandra.utils.*;
import org.apache.cassandra.Util;
import static org.apache.cassandra.db.context.CounterContext.ContextState;
public class CounterMutationTest extends SchemaLoader
{
@Test
public void testMergeOldShards() throws IOException
{
RowMutation rm;
CounterMutation cm;
CounterId id1 = CounterId.getLocalId();
rm = new RowMutation("Keyspace1", ByteBufferUtil.bytes("key1"));
rm.addCounter("Counter1", ByteBufferUtil.bytes("Column1"), 3);
cm = new CounterMutation(rm, ConsistencyLevel.ONE);
cm.apply();
CounterId.renewLocalId(2L); // faking time of renewal for test
CounterId id2 = CounterId.getLocalId();
rm = new RowMutation("Keyspace1", ByteBufferUtil.bytes("key1"));
rm.addCounter("Counter1", ByteBufferUtil.bytes("Column1"), 4);
cm = new CounterMutation(rm, ConsistencyLevel.ONE);
cm.apply();
CounterId.renewLocalId(4L); // faking time of renewal for test
CounterId id3 = CounterId.getLocalId();
rm = new RowMutation("Keyspace1", ByteBufferUtil.bytes("key1"));
rm.addCounter("Counter1", ByteBufferUtil.bytes("Column1"), 5);
rm.addCounter("Counter1", ByteBufferUtil.bytes("Column2"), 1);
cm = new CounterMutation(rm, ConsistencyLevel.ONE);
cm.apply();
DecoratedKey dk = Util.dk("key1");
ColumnFamily cf = Util.getColumnFamily(Keyspace.open("Keyspace1"), dk, "Counter1");
// First merges old shards
CounterColumn.mergeAndRemoveOldShards(dk, cf, Integer.MIN_VALUE, Integer.MAX_VALUE, false);
long now = System.currentTimeMillis();
Column c = cf.getColumn(ByteBufferUtil.bytes("Column1"));
assert c != null;
assert c instanceof CounterColumn;
assert ((CounterColumn)c).total() == 12L;
ContextState s = new ContextState(c.value());
assert s.getCounterId().equals(id1);
assert s.getCount() == 0L;
assert -s.getClock() > now - 1000 : " >";
assert -s.getClock() <= now;
s.moveToNext();
assert s.getCounterId().equals(id2);
assert s.getCount() == 0L;
assert -s.getClock() > now - 1000;
assert -s.getClock() <= now;
s.moveToNext();
assert s.getCounterId().equals(id3);
assert s.getCount() == 12L;
// Then collect old shards
CounterColumn.mergeAndRemoveOldShards(dk, cf, Integer.MAX_VALUE, Integer.MIN_VALUE, false);
c = cf.getColumn(ByteBufferUtil.bytes("Column1"));
assert c != null;
assert c instanceof CounterColumn;
assert ((CounterColumn)c).total() == 12L;
s = new ContextState(c.value());
assert s.getCounterId().equals(id3);
assert s.getCount() == 12L;
}
@Test
public void testGetOldShardFromSystemKeyspace() throws IOException
{
// Renewing a bunch of times and checking we get the same thing from
// the system keyspace that what is in memory
CounterId.renewLocalId();
CounterId.renewLocalId();
CounterId.renewLocalId();
List<CounterId.CounterIdRecord> inMem = CounterId.getOldLocalCounterIds();
List<CounterId.CounterIdRecord> onDisk = SystemKeyspace.getOldLocalCounterIds();
assert inMem.equals(onDisk);
}
@Test
public void testRemoveOldShardFixCorrupted() throws IOException
{
CounterContext ctx = CounterContext.instance();
int now = (int) (System.currentTimeMillis() / 1000);
// Check that corrupted context created prior to #2968 are fixed by removeOldShards
CounterId id1 = CounterId.getLocalId();
CounterId.renewLocalId();
CounterId id2 = CounterId.getLocalId();
ContextState state = ContextState.allocate(3, 2);
state.writeElement(CounterId.fromInt(1), 1, 4, false);
state.writeElement(id1, 3, 2, true);
state.writeElement(id2, -100, 5, true); // corrupted!
assert ctx.total(state.context) == 11;
try
{
ByteBuffer merger = ctx.computeOldShardMerger(state.context, Collections.<CounterId.CounterIdRecord>emptyList(), 0);
ctx.removeOldShards(ctx.merge(state.context, merger, HeapAllocator.instance), now);
fail("RemoveOldShards should throw an exception if the current id is non-sensical");
}
catch (RuntimeException e) {}
CounterId.renewLocalId();
ByteBuffer merger = ctx.computeOldShardMerger(state.context, Collections.<CounterId.CounterIdRecord>emptyList(), 0);
ByteBuffer cleaned = ctx.removeOldShards(ctx.merge(state.context, merger, HeapAllocator.instance), now);
assert ctx.total(cleaned) == 11;
// Check it is not corrupted anymore
ContextState state2 = new ContextState(cleaned);
while (state2.hasRemaining())
{
assert state2.getClock() >= 0 || state2.getCount() == 0;
state2.moveToNext();
}
// Check that if we merge old and clean on another node, we keep the right count
ByteBuffer onRemote = ctx.merge(ctx.clearAllDelta(state.context), ctx.clearAllDelta(cleaned), HeapAllocator.instance);
assert ctx.total(onRemote) == 11;
}
}