package org.infinispan.persistence.remote.upgrade;
import static org.infinispan.persistence.remote.upgrade.HotRodMigratorHelper.MIGRATION_MANAGER_HOT_ROD_KNOWN_KEYS;
import static org.infinispan.persistence.remote.upgrade.HotRodMigratorHelper.awaitTermination;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import org.infinispan.Cache;
import org.infinispan.client.hotrod.MetadataValue;
import org.infinispan.client.hotrod.RemoteCache;
import org.infinispan.commons.CacheException;
import org.infinispan.commons.io.UnsignedNumeric;
import org.infinispan.commons.marshall.AbstractExternalizer;
import org.infinispan.commons.marshall.Marshaller;
import org.infinispan.commons.marshall.WrappedByteArray;
import org.infinispan.commons.util.CloseableIterator;
import org.infinispan.commons.util.Util;
import org.infinispan.commons.util.concurrent.ConcurrentHashSet;
import org.infinispan.container.versioning.NumericVersion;
import org.infinispan.context.Flag;
import org.infinispan.distexec.DistributedCallable;
import org.infinispan.factories.ComponentRegistry;
import org.infinispan.factories.threads.DefaultThreadFactory;
import org.infinispan.metadata.EmbeddedMetadata;
import org.infinispan.metadata.Metadata;
import org.infinispan.metadata.impl.InternalMetadataImpl;
import org.infinispan.notifications.Listener;
import org.infinispan.notifications.cachelistener.annotation.CacheEntryRemoved;
import org.infinispan.notifications.cachelistener.event.CacheEntryRemovedEvent;
import org.infinispan.persistence.manager.PersistenceManager;
import org.infinispan.persistence.remote.RemoteStore;
import org.infinispan.persistence.remote.configuration.RemoteStoreConfiguration;
import org.infinispan.persistence.remote.logging.Log;
import org.infinispan.util.logging.LogFactory;
public class MigrationTask implements DistributedCallable<Object, Object, Integer> {
private static final Log log = LogFactory.getLog(MigrationTask.class, Log.class);
private static final String THREAD_NAME = "RollingUpgrade-MigrationTask";
private final Set<Integer> segments;
private final int readBatch;
private final int threads;
private final ConcurrentHashSet<WrappedByteArray> deletedKeys = new ConcurrentHashSet<>();
private byte[] ignoredKey;
private transient Set<RemoteStore> stores;
private transient Cache<Object, Object> cache;
private transient ExecutorService executorService;
private transient final RemoveListener listener = new RemoveListener();
public MigrationTask(Set<Integer> segments, int readBatch, int threads) {
this.segments = segments;
this.readBatch = readBatch;
this.threads = threads;
}
@Listener(clustered = true)
@SuppressWarnings("unused")
private class RemoveListener {
@CacheEntryRemoved
public void entryRemoved(CacheEntryRemovedEvent event) {
deletedKeys.add(new WrappedByteArray((byte[]) event.getKey()));
}
}
@Override
public Integer call() throws Exception {
try {
Iterator<RemoteStore> storeIterator = stores.iterator();
if (storeIterator.hasNext()) {
RemoteStore store = storeIterator.next();
RemoteCache<Object, Object> storeCache = store.getRemoteCache();
RemoteStoreConfiguration storeConfig = store.getConfiguration();
if (!storeConfig.hotRodWrapping()) {
throw log.remoteStoreNoHotRodWrapping(cache.getName());
}
AtomicInteger counter = new AtomicInteger(0);
migrateEntriesWithMetadata(storeCache, counter);
awaitTermination(executorService);
return counter.intValue();
}
return null;
} finally {
cache.removeListener(listener);
if (executorService != null) {
executorService.shutdownNow();
}
}
}
private void migrateEntriesWithMetadata(RemoteCache<Object, Object> sourceCache, AtomicInteger counter) {
try (CloseableIterator<Map.Entry<Object, MetadataValue<Object>>> iterator = sourceCache.retrieveEntriesWithMetadata(segments, readBatch)) {
while (iterator.hasNext() && !Thread.currentThread().isInterrupted()) {
Map.Entry<Object, MetadataValue<Object>> entry = iterator.next();
if (!Arrays.equals((byte[]) entry.getKey(), ignoredKey)) {
MetadataValue<Object> metadataValue = entry.getValue();
int lifespan = metadataValue.getLifespan();
int maxIdle = metadataValue.getMaxIdle();
long version = metadataValue.getVersion();
long created = metadataValue.getCreated();
long lastUsed = metadataValue.getLastUsed();
Metadata metadata = new EmbeddedMetadata.Builder()
.version(new NumericVersion(version))
.lifespan(lifespan, TimeUnit.SECONDS)
.maxIdle(maxIdle, TimeUnit.SECONDS)
.build();
InternalMetadataImpl internalMetadata = new InternalMetadataImpl(metadata, created, lastUsed);
executorService.submit(() -> {
Object key = entry.getKey();
if (!deletedKeys.contains(new WrappedByteArray((byte[]) key))) {
cache.getAdvancedCache().withFlags(Flag.SKIP_CACHE_LOAD, Flag.ROLLING_UPGRADE).putIfAbsent(entry.getKey(), entry.getValue().getValue(), internalMetadata);
}
int currentCount = counter.incrementAndGet();
if (log.isDebugEnabled() && currentCount % 100 == 0)
log.debugf(">> Migrated %s entries\n", currentCount);
});
}
}
}
}
@Override
public void setEnvironment(Cache<Object, Object> cache, Set<Object> inputKeys) {
ComponentRegistry cr = cache.getAdvancedCache().getComponentRegistry();
PersistenceManager loaderManager = cr.getComponent(PersistenceManager.class);
this.stores = loaderManager.getStores(RemoteStore.class);
Marshaller marshaller = new MigrationMarshaller();
this.cache = cache;
this.cache.addFilteredListener(listener, new RemovedFilter<>(), null, Util.asSet(CacheEntryRemoved.class));
DefaultThreadFactory threadFactory = new DefaultThreadFactory(null, 1, THREAD_NAME + "-%t", null, null);
this.executorService = Executors.newFixedThreadPool(threads, threadFactory);
try {
this.ignoredKey = marshaller.objectToByteBuffer(MIGRATION_MANAGER_HOT_ROD_KNOWN_KEYS);
} catch (Exception e) {
throw new CacheException(e);
}
}
public static class Externalizer extends AbstractExternalizer<MigrationTask> {
@Override
public Set<Class<? extends MigrationTask>> getTypeClasses() {
return Collections.singleton(MigrationTask.class);
}
@Override
public void writeObject(ObjectOutput output, MigrationTask task) throws IOException {
UnsignedNumeric.writeUnsignedInt(output, task.readBatch);
UnsignedNumeric.writeUnsignedInt(output, task.threads);
BitSet bs = new BitSet();
for (Integer segment : task.segments) {
bs.set(segment);
}
byte[] bytes = bs.toByteArray();
UnsignedNumeric.writeUnsignedInt(output, bytes.length);
output.write(bs.toByteArray());
}
@Override
public MigrationTask readObject(ObjectInput input) throws IOException, ClassNotFoundException {
int readBatch = UnsignedNumeric.readUnsignedInt(input);
int threads = UnsignedNumeric.readUnsignedInt(input);
int segmentsSize = UnsignedNumeric.readUnsignedInt(input);
byte[] bytes = new byte[segmentsSize];
input.read(bytes);
BitSet bitSet = BitSet.valueOf(bytes);
Set<Integer> segments = bitSet.stream().boxed().collect(Collectors.toSet());
return new MigrationTask(segments, readBatch, threads);
}
}
}