/* * * 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.context; import static org.junit.Assert.*; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; import org.junit.Test; import org.apache.cassandra.Util; import org.apache.cassandra.db.context.IContext.ContextRelationship; import static org.apache.cassandra.db.context.CounterContext.ContextState; import org.apache.cassandra.utils.*; import com.google.common.util.concurrent.Uninterruptibles; public class CounterContextTest { 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; static { idLength = CounterId.LENGTH; // size of int clockLength = 8; // size of long countLength = 8; // size of long stepLength = idLength + clockLength + countLength; } /** Allocates 1 byte from a new SlabAllocator and returns it. */ private Allocator bumpedSlab() { SlabAllocator allocator = new SlabAllocator(); allocator.allocate(1); return allocator; } @Test public void testCreate() { runCreate(HeapAllocator.instance); runCreate(bumpedSlab()); } private void runCreate(Allocator allocator) { ByteBuffer bytes = cc.create(4, allocator); assertEquals(stepLength + 4, bytes.remaining()); } @Test public void testDiff() { runDiff(HeapAllocator.instance); runDiff(bumpedSlab()); } private void runDiff(Allocator allocator) { ContextState left = ContextState.allocate(3, 0, allocator); ContextState right; // equality: equal nodes, all counts same left.writeElement(CounterId.fromInt(3), 3L, 0L); left.writeElement(CounterId.fromInt(6), 2L, 0L); left.writeElement(CounterId.fromInt(9), 1L, 0L); right = new ContextState(ByteBufferUtil.clone(left.context), left.headerLength); assert ContextRelationship.EQUAL == cc.diff(left.context, right.context); // greater than: left has superset of nodes (counts equal) left = ContextState.allocate(4, 0, allocator); left.writeElement(CounterId.fromInt(3), 3L, 0L); left.writeElement(CounterId.fromInt(6), 2L, 0L); left.writeElement(CounterId.fromInt(9), 1L, 0L); left.writeElement(CounterId.fromInt(12), 0L, 0L); right = ContextState.allocate(3, 0, allocator); right.writeElement(CounterId.fromInt(3), 3L, 0L); right.writeElement(CounterId.fromInt(6), 2L, 0L); right.writeElement(CounterId.fromInt(9), 1L, 0L); assert ContextRelationship.GREATER_THAN == cc.diff(left.context, right.context); // less than: left has subset of nodes (counts equal) left = ContextState.allocate(3, 0, allocator); left.writeElement(CounterId.fromInt(3), 3L, 0L); left.writeElement(CounterId.fromInt(6), 2L, 0L); left.writeElement(CounterId.fromInt(9), 1L, 0L); right = ContextState.allocate(4, 0, allocator); right.writeElement(CounterId.fromInt(3), 3L, 0L); right.writeElement(CounterId.fromInt(6), 2L, 0L); right.writeElement(CounterId.fromInt(9), 1L, 0L); right.writeElement(CounterId.fromInt(12), 0L, 0L); assert ContextRelationship.LESS_THAN == cc.diff(left.context, right.context); // greater than: equal nodes, but left has higher counts left = ContextState.allocate(3, 0, allocator); left.writeElement(CounterId.fromInt(3), 3L, 0L); left.writeElement(CounterId.fromInt(6), 2L, 0L); left.writeElement(CounterId.fromInt(9), 3L, 0L); right = ContextState.allocate(3, 0, allocator); right.writeElement(CounterId.fromInt(3), 3L, 0L); right.writeElement(CounterId.fromInt(6), 2L, 0L); right.writeElement(CounterId.fromInt(9), 1L, 0L); assert ContextRelationship.GREATER_THAN == cc.diff(left.context, right.context); // less than: equal nodes, but right has higher counts left = ContextState.allocate(3, 0, allocator); left.writeElement(CounterId.fromInt(3), 3L, 0L); left.writeElement(CounterId.fromInt(6), 2L, 0L); left.writeElement(CounterId.fromInt(9), 3L, 0L); right = ContextState.allocate(3, 0, allocator); right.writeElement(CounterId.fromInt(3), 3L, 0L); right.writeElement(CounterId.fromInt(6), 9L, 0L); right.writeElement(CounterId.fromInt(9), 3L, 0L); assert ContextRelationship.LESS_THAN == cc.diff(left.context, right.context); // disjoint: right and left have disjoint node sets left = ContextState.allocate(3, 0, allocator); left.writeElement(CounterId.fromInt(3), 1L, 0L); left.writeElement(CounterId.fromInt(4), 1L, 0L); left.writeElement(CounterId.fromInt(9), 1L, 0L); right = ContextState.allocate(3, 0, allocator); right.writeElement(CounterId.fromInt(3), 1L, 0L); right.writeElement(CounterId.fromInt(6), 1L, 0L); right.writeElement(CounterId.fromInt(9), 1L, 0L); assert ContextRelationship.DISJOINT == cc.diff(left.context, right.context); left = ContextState.allocate(3, 0, allocator); left.writeElement(CounterId.fromInt(3), 1L, 0L); left.writeElement(CounterId.fromInt(4), 1L, 0L); left.writeElement(CounterId.fromInt(9), 1L, 0L); right = ContextState.allocate(3, 0, allocator); right.writeElement(CounterId.fromInt(2), 1L, 0L); right.writeElement(CounterId.fromInt(6), 1L, 0L); right.writeElement(CounterId.fromInt(12), 1L, 0L); assert ContextRelationship.DISJOINT == cc.diff(left.context, right.context); // disjoint: equal nodes, but right and left have higher counts in differing nodes left = ContextState.allocate(3, 0, allocator); left.writeElement(CounterId.fromInt(3), 1L, 0L); left.writeElement(CounterId.fromInt(6), 3L, 0L); left.writeElement(CounterId.fromInt(9), 1L, 0L); right = ContextState.allocate(3, 0, allocator); right.writeElement(CounterId.fromInt(3), 1L, 0L); right.writeElement(CounterId.fromInt(6), 1L, 0L); right.writeElement(CounterId.fromInt(9), 5L, 0L); assert ContextRelationship.DISJOINT == cc.diff(left.context, right.context); left = ContextState.allocate(3, 0, allocator); left.writeElement(CounterId.fromInt(3), 2L, 0L); left.writeElement(CounterId.fromInt(6), 3L, 0L); left.writeElement(CounterId.fromInt(9), 1L, 0L); right = ContextState.allocate(3, 0, allocator); right.writeElement(CounterId.fromInt(3), 1L, 0L); right.writeElement(CounterId.fromInt(6), 9L, 0L); right.writeElement(CounterId.fromInt(9), 5L, 0L); assert ContextRelationship.DISJOINT == cc.diff(left.context, right.context); // disjoint: left has more nodes, but lower counts left = ContextState.allocate(4, 0, allocator); left.writeElement(CounterId.fromInt(3), 2L, 0L); left.writeElement(CounterId.fromInt(6), 3L, 0L); left.writeElement(CounterId.fromInt(9), 1L, 0L); left.writeElement(CounterId.fromInt(12), 1L, 0L); right = ContextState.allocate(3, 0, allocator); right.writeElement(CounterId.fromInt(3), 4L, 0L); right.writeElement(CounterId.fromInt(6), 9L, 0L); right.writeElement(CounterId.fromInt(9), 5L, 0L); assert ContextRelationship.DISJOINT == cc.diff(left.context, right.context); // disjoint: left has less nodes, but higher counts left = ContextState.allocate(3, 0, allocator); left.writeElement(CounterId.fromInt(3), 5L, 0L); left.writeElement(CounterId.fromInt(6), 3L, 0L); left.writeElement(CounterId.fromInt(9), 2L, 0L); right = ContextState.allocate(4, 0, allocator); right.writeElement(CounterId.fromInt(3), 4L, 0L); right.writeElement(CounterId.fromInt(6), 3L, 0L); right.writeElement(CounterId.fromInt(9), 2L, 0L); right.writeElement(CounterId.fromInt(12), 1L, 0L); assert ContextRelationship.DISJOINT == cc.diff(left.context, right.context); // disjoint: mixed nodes and counts left = ContextState.allocate(3, 0, allocator); left.writeElement(CounterId.fromInt(3), 5L, 0L); left.writeElement(CounterId.fromInt(6), 2L, 0L); left.writeElement(CounterId.fromInt(9), 2L, 0L); right = ContextState.allocate(4, 0, allocator); right.writeElement(CounterId.fromInt(3), 4L, 0L); right.writeElement(CounterId.fromInt(6), 3L, 0L); right.writeElement(CounterId.fromInt(9), 2L, 0L); right.writeElement(CounterId.fromInt(12), 1L, 0L); assert ContextRelationship.DISJOINT == cc.diff(left.context, right.context); left = ContextState.allocate(4, 0, allocator); left.writeElement(CounterId.fromInt(3), 5L, 0L); left.writeElement(CounterId.fromInt(6), 2L, 0L); left.writeElement(CounterId.fromInt(7), 2L, 0L); left.writeElement(CounterId.fromInt(9), 2L, 0L); right = ContextState.allocate(3, 0, allocator); right.writeElement(CounterId.fromInt(3), 4L, 0L); right.writeElement(CounterId.fromInt(6), 3L, 0L); right.writeElement(CounterId.fromInt(9), 2L, 0L); assert ContextRelationship.DISJOINT == cc.diff(left.context, right.context); } @Test public void testMerge() { runMerge(HeapAllocator.instance); runMerge(bumpedSlab()); } private void runMerge(Allocator allocator) { // note: local counts aggregated; remote counts are reconciled (i.e. take max) ContextState left = ContextState.allocate(4, 1, allocator); left.writeElement(CounterId.fromInt(1), 1L, 1L); left.writeElement(CounterId.fromInt(2), 2L, 2L); left.writeElement(CounterId.fromInt(4), 6L, 3L); left.writeElement(CounterId.getLocalId(), 7L, 3L, true); ContextState right = ContextState.allocate(3, 1, allocator); right.writeElement(CounterId.fromInt(4), 4L, 4L); right.writeElement(CounterId.fromInt(5), 5L, 5L); right.writeElement(CounterId.getLocalId(), 2L, 9L, true); ByteBuffer merged = cc.merge(left.context, right.context, allocator); int hd = 4; assertEquals(hd + 5 * stepLength, merged.remaining()); // local node id's counts are aggregated assert Util.equalsCounterId(CounterId.getLocalId(), merged, hd + 4 * stepLength); assertEquals( 9L, merged.getLong(merged.position() + hd + 4*stepLength + idLength)); assertEquals(12L, merged.getLong(merged.position() + hd + 4*stepLength + idLength + clockLength)); // remote node id counts are reconciled (i.e. take max) assert Util.equalsCounterId(CounterId.fromInt(4), merged, hd + 2 * stepLength); assertEquals( 6L, merged.getLong(merged.position() + hd + 2*stepLength + idLength)); assertEquals( 3L, merged.getLong(merged.position() + hd + 2*stepLength + idLength + clockLength)); assert Util.equalsCounterId(CounterId.fromInt(5), merged, hd + 3 * stepLength); assertEquals( 5L, merged.getLong(merged.position() + hd + 3*stepLength + idLength)); assertEquals( 5L, merged.getLong(merged.position() + hd + 3*stepLength + idLength + clockLength)); assert Util.equalsCounterId(CounterId.fromInt(2), merged, hd + 1 * stepLength); assertEquals( 2L, merged.getLong(merged.position() + hd + 1*stepLength + idLength)); assertEquals( 2L, merged.getLong(merged.position() + hd + 1*stepLength + idLength + clockLength)); assert Util.equalsCounterId(CounterId.fromInt(1), merged, hd + 0 * stepLength); assertEquals( 1L, merged.getLong(merged.position() + hd + 0*stepLength + idLength)); assertEquals( 1L, merged.getLong(merged.position() + hd + 0*stepLength + idLength + clockLength)); } @Test public void testTotal() { runTotal(HeapAllocator.instance); runTotal(bumpedSlab()); } private void runTotal(Allocator allocator) { ContextState left = ContextState.allocate(4, 1, allocator); left.writeElement(CounterId.fromInt(1), 1L, 1L); left.writeElement(CounterId.fromInt(2), 2L, 2L); left.writeElement(CounterId.fromInt(4), 3L, 3L); left.writeElement(CounterId.getLocalId(), 3L, 3L, true); ContextState right = ContextState.allocate(3, 1, allocator); right.writeElement(CounterId.fromInt(4), 4L, 4L); right.writeElement(CounterId.fromInt(5), 5L, 5L); right.writeElement(CounterId.getLocalId(), 9L, 9L, true); ByteBuffer merged = cc.merge(left.context, right.context, allocator); // 127.0.0.1: 12 (3+9) // 0.0.0.1: 1 // 0.0.0.2: 2 // 0.0.0.4: 4 // 0.0.0.5: 5 assertEquals(24L, cc.total(merged)); } @Test public void testMergeOldShards() { runMergeOldShards(HeapAllocator.instance); runMergeOldShards(bumpedSlab()); } private void runMergeOldShards(Allocator allocator) { long now = System.currentTimeMillis(); CounterId id1 = CounterId.fromInt(1); CounterId id3 = CounterId.fromInt(3); List<CounterId.CounterIdRecord> records = new ArrayList<CounterId.CounterIdRecord>(); records.add(new CounterId.CounterIdRecord(id1, 2L)); records.add(new CounterId.CounterIdRecord(id3, 4L)); ContextState ctx = ContextState.allocate(5, 3, allocator); ctx.writeElement(id1, 1L, 1L, true); ctx.writeElement(CounterId.fromInt(2), 2L, 2L); ctx.writeElement(id3, 3L, 3L, true); ctx.writeElement(CounterId.fromInt(4), 6L, 3L); ctx.writeElement(CounterId.fromInt(5), 7L, 3L, true); ByteBuffer merger = cc.computeOldShardMerger(ctx.context, records, Integer.MAX_VALUE); ContextState m = new ContextState(merger); assert m.getCounterId().equals(id1); assert m.getClock() <= -now; assert m.getCount() == -1L; assert m.isDelta(); m.moveToNext(); assert m.getCounterId().equals(id3); assert m.getClock() <= -now; assert m.getCount() == -3L; assert m.isDelta(); m.moveToNext(); assert m.getCounterId().equals(CounterId.getLocalId()); assert m.getClock() == 1L; assert m.getCount() == 4L; assert m.isDelta(); assert cc.total(ctx.context) == cc.total(cc.merge(ctx.context, merger, allocator)); } @Test public void testRemoveOldShards() { runRemoveOldShards(HeapAllocator.instance); runRemoveOldShards(bumpedSlab()); } private void runRemoveOldShards(Allocator allocator) { CounterId id1 = CounterId.fromInt(1); CounterId id3 = CounterId.fromInt(3); CounterId id6 = CounterId.fromInt(6); List<CounterId.CounterIdRecord> records = new ArrayList<CounterId.CounterIdRecord>(); records.add(new CounterId.CounterIdRecord(id1, 2L)); records.add(new CounterId.CounterIdRecord(id3, 4L)); records.add(new CounterId.CounterIdRecord(id6, 10L)); ContextState ctx = ContextState.allocate(6, 3, allocator); ctx.writeElement(id1, 1L, 1L, true); ctx.writeElement(CounterId.fromInt(2), 2L, 2L); ctx.writeElement(id3, 3L, 3L, true); ctx.writeElement(CounterId.fromInt(4), 6L, 3L); ctx.writeElement(CounterId.fromInt(5), 7L, 3L, true); ctx.writeElement(id6, 5L, 6L); ByteBuffer merger = cc.computeOldShardMerger(ctx.context, records, Integer.MAX_VALUE); ByteBuffer merged = cc.merge(ctx.context, merger, allocator); assert cc.total(ctx.context) == cc.total(merged); ByteBuffer cleaned = cc.removeOldShards(merged, (int)(System.currentTimeMillis() / 1000) + 1); assert cc.total(ctx.context) == cc.total(cleaned); assert cleaned.remaining() == ctx.context.remaining() - stepLength - 2; } @Test public void testRemoveOldShardsNotAllExpiring() { runRemoveOldShardsNotAllExpiring(HeapAllocator.instance); runRemoveOldShardsNotAllExpiring(bumpedSlab()); } private void runRemoveOldShardsNotAllExpiring(Allocator allocator) { CounterId id1 = CounterId.fromInt(1); CounterId id3 = CounterId.fromInt(3); CounterId id6 = CounterId.fromInt(6); List<CounterId.CounterIdRecord> records = new ArrayList<CounterId.CounterIdRecord>(); records.add(new CounterId.CounterIdRecord(id1, 2L)); records.add(new CounterId.CounterIdRecord(id3, 4L)); records.add(new CounterId.CounterIdRecord(id6, 10L)); ContextState ctx = ContextState.allocate(6, 3, allocator); ctx.writeElement(id1, 0L, 1L, true); ctx.writeElement(CounterId.fromInt(2), 0L, 2L); ctx.writeElement(id3, 0L, 3L, true); ctx.writeElement(CounterId.fromInt(4), 0L, 3L); ctx.writeElement(CounterId.fromInt(5), 0L, 3L, true); ctx.writeElement(id6, 0L, 6L); int timeFirstMerge = (int)(System.currentTimeMillis() / 1000); // First, only merge the first id ByteBuffer merger = cc.computeOldShardMerger(ctx.context, records, 3L); ByteBuffer merged = cc.merge(ctx.context, merger, allocator); assert cc.total(ctx.context) == cc.total(merged); Uninterruptibles.sleepUninterruptibly(2, TimeUnit.SECONDS); // merge the second one ByteBuffer merger2 = cc.computeOldShardMerger(merged, records, 7L); ByteBuffer merged2 = cc.merge(merged, merger2, allocator); assert cc.total(ctx.context) == cc.total(merged2); ByteBuffer cleaned = cc.removeOldShards(merged2, timeFirstMerge + 1); assert cc.total(ctx.context) == cc.total(cleaned); assert cleaned.remaining() == ctx.context.remaining(); // We should have cleaned id1 but not id3 ContextState m = new ContextState(cleaned); m.moveToNext(); assert m.getCounterId().equals(id3); } @Test public void testRemoveNotDeltaOldShards() { runRemoveNotDeltaOldShards(HeapAllocator.instance); runRemoveNotDeltaOldShards(bumpedSlab()); } private void runRemoveNotDeltaOldShards(Allocator allocator) { ContextState ctx = ContextState.allocate(4, 1, allocator); ctx.writeElement(CounterId.fromInt(1), 1L, 1L, true); ctx.writeElement(CounterId.fromInt(2), -System.currentTimeMillis(), 0L); ctx.writeElement(CounterId.fromInt(3), -System.currentTimeMillis(), 0L); ctx.writeElement(CounterId.fromInt(4), -System.currentTimeMillis(), 0L); ByteBuffer cleaned = cc.removeOldShards(ctx.context, (int)(System.currentTimeMillis() / 1000) + 1); assert cc.total(ctx.context) == cc.total(cleaned); assert cleaned.remaining() == ctx.context.remaining() - 3 * stepLength; } }