package com.jivesoftware.os.amza.service;
import com.google.common.collect.Interner;
import com.google.common.collect.Interners;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.jivesoftware.os.amza.api.AmzaInterner;
import com.jivesoftware.os.amza.api.TimestampedValue;
import com.jivesoftware.os.amza.api.filer.UIO;
import com.jivesoftware.os.amza.api.partition.RingMembership;
import com.jivesoftware.os.amza.api.ring.RingHost;
import com.jivesoftware.os.amza.api.ring.RingMember;
import com.jivesoftware.os.amza.api.ring.RingMemberAndHost;
import com.jivesoftware.os.amza.api.ring.TimestampedRingHost;
import com.jivesoftware.os.amza.api.wal.WALKey;
import com.jivesoftware.os.amza.service.ring.AmzaRingReader;
import com.jivesoftware.os.amza.service.ring.CacheId;
import com.jivesoftware.os.amza.service.ring.RingSet;
import com.jivesoftware.os.amza.service.ring.RingTopology;
import com.jivesoftware.os.amza.service.storage.PartitionCreator;
import com.jivesoftware.os.amza.service.storage.PartitionIndex;
import com.jivesoftware.os.amza.service.storage.PartitionStore;
import com.jivesoftware.os.aquarium.Member;
import com.jivesoftware.os.jive.utils.collections.bah.BAHasher;
import com.jivesoftware.os.jive.utils.collections.bah.ConcurrentBAHash;
import com.jivesoftware.os.mlogger.core.MetricLogger;
import com.jivesoftware.os.mlogger.core.MetricLoggerFactory;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
public class AmzaRingStoreReader implements AmzaRingReader, RingMembership {
private static final MetricLogger LOG = MetricLoggerFactory.getLogger();
private final AmzaSystemReady systemReady;
private final AmzaInterner amzaInterner;
private final RingMember rootRingMember;
private PartitionStore ringIndex;
private PartitionStore nodeIndex;
private final ConcurrentBAHash<CacheId<RingTopology>> ringsCache;
private final ConcurrentBAHash<CacheId<RingSet>> ringMemberRingNamesCache;
private final AtomicLong nodeCacheId;
private final Set<RingMember> blacklistRingMembers;
public AmzaRingStoreReader(AmzaSystemReady systemReady,
AmzaInterner amzaInterner,
RingMember rootRingMember,
ConcurrentBAHash<CacheId<RingTopology>> ringsCache,
ConcurrentBAHash<CacheId<RingSet>> ringMemberRingNamesCache,
AtomicLong nodeCacheId,
Set<RingMember> blacklistRingMembers) {
this.systemReady = systemReady;
this.amzaInterner = amzaInterner;
this.rootRingMember = rootRingMember;
this.ringsCache = ringsCache;
this.ringMemberRingNamesCache = ringMemberRingNamesCache;
this.nodeCacheId = nodeCacheId;
this.blacklistRingMembers = blacklistRingMembers;
}
public void start(PartitionIndex partitionIndex) throws Exception {
ringIndex = partitionIndex.getSystemPartition(PartitionCreator.RING_INDEX);
nodeIndex = partitionIndex.getSystemPartition(PartitionCreator.NODE_INDEX);
}
public void stop() {
ringIndex = null;
nodeIndex = null;
}
byte[] keyToRingName(WALKey walKey) throws IOException {
return UIO.readByteArray(walKey.key, 0, "ringName");
}
byte[] key(byte[] ringName, RingMember ringMember) throws Exception {
byte[] key = new byte[4 + ringName.length + 1 + ((ringMember != null) ? 4 + ringMember.sizeInBytes() : 0)];
int offset = 0;
UIO.intBytes(ringName.length, key, offset);
offset += 4;
UIO.writeBytes(ringName, key, offset);
offset += ringName.length;
offset++; // separator
if (ringMember != null) {
UIO.intBytes(ringMember.sizeInBytes(), key, offset);
offset += 4;
offset += ringMember.toBytes(key, offset);
}
return key;
}
RingMember keyToRingMember(byte[] key) throws Exception {
int o = 0;
o += UIO.bytesInt(key, o); // ringName
o += 4;
o++; // separator
int ringMemberLength = UIO.bytesInt(key, o);
o += 4;
return amzaInterner.internRingMember(key, o, ringMemberLength);
}
@Override
public RingMember getRingMember() {
return rootRingMember;
}
public TimestampedRingHost getRingHost() throws Exception {
if (ringIndex == null || nodeIndex == null) {
throw new IllegalStateException("Ring store reader wasn't opened or has already been closed.");
}
TimestampedValue registeredHost = nodeIndex.getTimestampedValue(null, rootRingMember.toBytes());
if (registeredHost != null) {
return new TimestampedRingHost(RingHost.fromBytes(registeredHost.getValue()), registeredHost.getTimestampId());
} else {
return new TimestampedRingHost(RingHost.UNKNOWN_RING_HOST, -1);
}
}
@Override
public boolean isMemberOfRing(byte[] ringName, long timeoutInMillis) throws Exception {
RingTopology ring = getRing(ringName, timeoutInMillis);
return ring.rootMemberIndex >= 0;
}
private final Interner<RingMemberAndHost> ringMemberAndHostInterner = Interners.newStrongInterner();
@Override
public RingTopology getRing(byte[] ringName, long timeoutInMillis) throws Exception {
if (ringIndex == null || nodeIndex == null) {
throw new IllegalStateException("Ring store reader wasn't opened or has already been closed.");
}
if (timeoutInMillis >= 0) {
systemReady.await(timeoutInMillis);
}
CacheId<RingTopology> cacheIdRingTopology = ringsCache.computeIfAbsent(ringName, key -> new CacheId<>(null));
RingTopology ring = cacheIdRingTopology.entry;
long currentRingCacheId = cacheIdRingTopology.currentCacheId;
long currentNodeCacheId = nodeCacheId.get();
if (ring == null || ring.ringCacheId != currentRingCacheId || ring.nodeCacheId != currentNodeCacheId) {
try {
List<RingMemberAndHost> orderedRing = Lists.newArrayList();
Set<Member> aquariumMembers = Sets.newHashSet();
int[] rootMemberIndex = { -1 };
byte[] from = key(ringName, null);
nodeIndex.streamValues(null,
stream -> ringIndex.rangeScan(null,
from,
null,
WALKey.prefixUpperExclusive(from),
(prefix, key, value, valueTimestamp, valueTombstone, valueVersion) -> {
if (!valueTombstone) {
RingMember ringMember = keyToRingMember(key);
if (blacklistRingMembers.contains(ringMember)) {
return true;
} else {
return stream.stream(ringMember.toBytes());
}
} else {
return true;
}
}, true),
(prefix, key, value, valueTimestamp, valueTombstone, valueVersion) -> {
RingMember ringMember = amzaInterner.internRingMember(key, 0, key.length);
if (!blacklistRingMembers.contains(ringMember)) {
if (ringMember.equals(rootRingMember)) {
rootMemberIndex[0] = orderedRing.size();
}
if (value != null && !valueTombstone) {
orderedRing.add(internMemberAndHost(ringMember, RingHost.fromBytes(value)));
} else {
orderedRing.add(internMemberAndHost(ringMember, RingHost.UNKNOWN_RING_HOST));
}
aquariumMembers.add(ringMember.asAquariumMember());
}
return true;
});
boolean system = Arrays.equals(AmzaRingReader.SYSTEM_RING, ringName);
ring = new RingTopology(system, currentRingCacheId, currentNodeCacheId, orderedRing, aquariumMembers, rootMemberIndex[0]);
cacheIdRingTopology.entry = ring;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return ring;
}
public void streamRingMembersAndHosts(RingMemberAndHostStream stream) throws Exception {
if (ringIndex == null || nodeIndex == null) {
throw new IllegalStateException("Ring store reader wasn't opened or has already been closed.");
}
nodeIndex.rowScan((prefix, key, value, valueTimestamp, valueTombstoned, valueVersion) -> {
RingMember ringMember = amzaInterner.internRingMember(key, 0, key.length);
if (valueTombstoned || blacklistRingMembers.contains(ringMember)) {
return true;
} else if (value != null) {
return stream.stream(internMemberAndHost(ringMember, RingHost.fromBytes(value)));
} else {
return stream.stream(internMemberAndHost(ringMember, RingHost.UNKNOWN_RING_HOST));
}
}, true);
}
private RingMemberAndHost internMemberAndHost(RingMember ringMember, RingHost ringHost) {
return ringMemberAndHostInterner.intern(new RingMemberAndHost(ringMember, ringHost));
}
public interface RingMemberAndHostStream {
boolean stream(RingMemberAndHost ringMemberAndHost) throws Exception;
}
public RingHost getRingHost(RingMember ringMember) throws Exception {
if (ringIndex == null || nodeIndex == null) {
throw new IllegalStateException("Ring store reader wasn't opened or has already been closed.");
}
TimestampedValue rawRingHost = nodeIndex.getTimestampedValue(null, ringMember.toBytes());
return rawRingHost == null ? null : RingHost.fromBytes(rawRingHost.getValue());
}
public Set<RingMember> getNeighboringRingMembers(byte[] ringName, long timeoutInMillis) throws Exception {
if (ringIndex == null || nodeIndex == null) {
throw new IllegalStateException("Ring store reader wasn't opened or has already been closed.");
}
if (timeoutInMillis >= 0) {
systemReady.await(timeoutInMillis);
}
byte[] from = key(ringName, null);
Set<RingMember> ring = Sets.newHashSet();
ringIndex.rangeScan(null,
from,
null,
WALKey.prefixUpperExclusive(from),
(prefix, key, value, valueTimestamp, valueTombstone, valueVersion) -> {
if (!valueTombstone) {
RingMember ringMember = keyToRingMember(key);
if (!blacklistRingMembers.contains(ringMember) && !ringMember.equals(rootRingMember)) {
ring.add(keyToRingMember(key));
}
}
return true;
}, true);
return ring;
}
@Override
public void streamRingNames(RingMember desiredRingMember, long timeoutInMillis, RingNameStream ringNameStream) throws Exception {
RingSet ringSet = getRingSet(desiredRingMember, timeoutInMillis);
ringSet.ringNames.stream(ringNameStream::stream);
}
@Override
public RingSet getRingSet(RingMember desiredRingMember, long timeoutInMillis) throws Exception {
if (ringIndex == null || nodeIndex == null) {
throw new IllegalStateException("Ring store reader wasn't opened or has already been closed.");
}
if (blacklistRingMembers.contains(desiredRingMember)) {
throw new IllegalArgumentException("Requested ring member is blacklisted");
}
if (timeoutInMillis >= 0) {
systemReady.await(timeoutInMillis);
}
CacheId<RingSet> cacheIdRingSet = ringMemberRingNamesCache.computeIfAbsent(desiredRingMember.leakBytes(), key -> new CacheId<>(null));
RingSet ringSet = cacheIdRingSet.entry;
long currentMemberCacheId = cacheIdRingSet.currentCacheId;
if (ringSet == null || ringSet.memberCacheId != currentMemberCacheId) {
try {
ConcurrentBAHash<Integer> ringNames = new ConcurrentBAHash<>(13, true, 1);
try {
ringIndex.rowScan((prefix, key, value, valueTimestamp, valueTombstone, valueVersion) -> {
if (!valueTombstone) {
int o = 0;
int ringNameLength = UIO.bytesInt(key, o);
o += 4;
byte[] ringName = amzaInterner.internRingName(key, o, ringNameLength);
o += ringNameLength;
o++; // separator
int ringMemberLength = UIO.bytesInt(key, o);
o += 4;
RingMember ringMember = amzaInterner.internRingMember(key, o, ringMemberLength);
if (ringMember != null && ringMember.equals(desiredRingMember)) {
ringNames.put(ringName, BAHasher.SINGLETON.hashCode(ringName, 0, ringName.length));
}
}
return true;
}, true);
} catch (Exception x) {
throw new RuntimeException(x);
}
ringSet = new RingSet(currentMemberCacheId, ringNames);
cacheIdRingSet.entry = ringSet;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return ringSet;
}
@Override
public int getRingSize(byte[] ringName, long timeoutInMillis) throws Exception {
return getRing(ringName, timeoutInMillis).entries.size();
}
@Override
public int getTakeFromFactor(byte[] ringName, long timeoutInMillis) throws Exception {
return getRing(ringName, timeoutInMillis).getTakeFromFactor();
}
@Override
public void allRings(RingStream ringStream) throws Exception {
if (ringIndex == null || nodeIndex == null) {
throw new IllegalStateException("Ring store reader wasn't opened or has already been closed.");
}
Map<RingMember, RingHost> ringMemberToRingHost = new HashMap<>();
nodeIndex.rowScan((prefix, key, value, valueTimestamp, valueTombstone, valueVersion) -> {
if (!valueTombstone) {
RingMember ringMember = new RingMember(key);
if (!blacklistRingMembers.contains(ringMember)) {
RingHost ringHost = RingHost.fromBytes(value);
ringMemberToRingHost.put(ringMember, ringHost);
}
}
return true;
}, true);
ringIndex.rowScan((prefix, key, value, valueTimestamp, valueTombstone, valueVersion) -> {
if (!valueTombstone) {
int o = 0;
int ringNameLength = UIO.bytesInt(key, o);
o += 4;
byte[] ringName = amzaInterner.internRingName(key, o, ringNameLength);
o += ringNameLength;
o++; // separator
int ringMemberLength = UIO.bytesInt(key, o);
o += 4;
RingMember ringMember = amzaInterner.internRingMember(key, o, ringMemberLength);
if (blacklistRingMembers.contains(ringMember)) {
return true;
} else {
RingHost ringHost = ringMemberToRingHost.get(ringMember);
if (ringHost == null) {
ringHost = RingHost.UNKNOWN_RING_HOST;
}
return ringStream.stream(ringName, ringMember, ringHost);
}
} else {
return true;
}
}, true);
}
}