All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.redislabs.riot.redis.AbstractTargetCommand Maven / Gradle / Ivy

The newest version!
package com.redislabs.riot.redis;

import com.redislabs.riot.AbstractFlushingTransferCommand;
import com.redislabs.riot.RedisOptions;
import com.redislabs.riot.RedisReaderOptions;
import com.redislabs.riot.RiotStepBuilder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.batch.core.ExitStatus;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.job.flow.Flow;
import org.springframework.batch.core.listener.StepExecutionListenerSupport;
import org.springframework.batch.core.step.builder.SimpleStepBuilder;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.core.step.tasklet.TaskletStep;
import org.springframework.batch.item.redis.DataStructureItemReader;
import org.springframework.batch.item.redis.support.*;
import picocli.CommandLine;

@Data
@EqualsAndHashCode(callSuper = true)
@Slf4j
public abstract class AbstractTargetCommand extends AbstractFlushingTransferCommand {

    private static final String ASCII_COMPARE_MESSAGE_FORMAT = ">%,d T%,d ≠%,d ⧗%,d <%,d";
    private static final String COLORFUL_COMPARE_MESSAGE_FORMAT = "\u001b[31m>%,d \u001b[33mT%,d \u001b[35m≠%,d \u001b[36m⧗%,d \u001b[34m<%,d\u001b[0m";

    @CommandLine.ArgGroup(exclusive = false, heading = "Target Redis connection options%n")
    protected RedisOptions targetRedisOptions = new RedisOptions();
    @CommandLine.ArgGroup(exclusive = false, heading = "Source Redis reader options%n")
    protected RedisReaderOptions readerOptions = new RedisReaderOptions();
    @CommandLine.Mixin
    private CompareOptions compareOptions = new CompareOptions();

    protected void initialMax(RiotStepBuilder step) {
        step.initialMax(readerOptions.initialMaxSupplier(getRedisOptions()));
    }

    protected Flow verificationFlow(StepBuilderFactory stepBuilderFactory) {
        KeyValueItemReader> sourceReader = dataStructureReader();
        log.debug("Creating key comparator with TTL tolerance of {} seconds", compareOptions.getTtlTolerance());
        DataStructureValueReader targetValueReader = targetDataStructureValueReader();
        KeyComparisonResultCounter counter = new KeyComparisonResultCounter<>();
        KeyComparisonItemWriter.KeyComparisonItemWriterBuilder writerBuilder = KeyComparisonItemWriter.valueReader(targetValueReader);
        writerBuilder.resultHandler(counter);
        if (compareOptions.isShowDiffs()) {
            writerBuilder.resultHandler(this::log);
        }
        writerBuilder.ttlTolerance(compareOptions.getTtlToleranceDuration());
        KeyComparisonItemWriter writer = writerBuilder.build();
        StepBuilder verificationStepBuilder = stepBuilderFactory.get("verification-step");
        RiotStepBuilder, DataStructure> stepBuilder = riotStep(verificationStepBuilder, "Verifying");
        initialMax(stepBuilder);
        stepBuilder.reader(sourceReader).writer(writer);
        stepBuilder.extraMessage(() -> extraMessage(counter));
        SimpleStepBuilder, DataStructure> step = stepBuilder.build();
        step.listener(new StepExecutionListenerSupport() {
            @Override
            public ExitStatus afterStep(StepExecution stepExecution) {
                if (counter.isOK()) {
                    log.info("Verification completed - all OK");
                    return super.afterStep(stepExecution);
                }
                try {
                    Thread.sleep(transferOptions.getProgressUpdateIntervalMillis());
                } catch (InterruptedException e) {
                    log.debug("Verification interrupted");
                    return null;
                }
                log.warn("Verification failed: identical={}, missing={}, extraneous={}, values={}, ttls={}, types={}", (Object[]) counter.get(KeyComparisonItemWriter.Result.OK, KeyComparisonItemWriter.Result.SOURCE, KeyComparisonItemWriter.Result.TARGET, KeyComparisonItemWriter.Result.VALUE, KeyComparisonItemWriter.Result.TTL, KeyComparisonItemWriter.Result.TYPE));
                return new ExitStatus(ExitStatus.FAILED.getExitCode(), "Verification failed");
            }
        });
        TaskletStep verificationStep = step.build();
        return flow("verification-flow").start(verificationStep).build();
    }

    private String extraMessage(KeyComparisonResultCounter counter) {
        Long[] counts = counter.get(KeyComparisonItemWriter.Result.SOURCE, KeyComparisonItemWriter.Result.TYPE, KeyComparisonItemWriter.Result.VALUE, KeyComparisonItemWriter.Result.TTL, KeyComparisonItemWriter.Result.TARGET);
        return " " + String.format(extraMessageFormat(), (Object[]) counts);
    }

    private String extraMessageFormat() {
        switch (transferOptions.getProgress()) {
            case COLOR:
                return COLORFUL_COMPARE_MESSAGE_FORMAT;
            default:
                return ASCII_COMPARE_MESSAGE_FORMAT;
        }
    }

    private void log(DataStructure source, DataStructure target, KeyComparisonItemWriter.Result result) {
        switch (result) {
            case OK:
                return;
            case SOURCE:
                log.warn("Key {} is missing from target", source.getKey());
                return;
            case TARGET:
                log.warn("Key {} in target but not in source", target.getKey());
                return;
            case TTL:
                log.warn("Key {} has different TTLs: source={} target={}", source.getKey(), source.getAbsoluteTTL(), target.getAbsoluteTTL());
                return;
            case TYPE:
                log.warn("Key {} has different types: source={} target={}", source.getKey(), source.getType(), target.getType());
                return;
            case VALUE:
                log.warn("Key {} has different values: source={} target={}", source.getKey(), source.getValue(), target.getValue());
                return;
        }
    }

    protected KeyValueItemReader> dataStructureReader() {
        RedisOptions redisOptions = getRedisOptions();
        if (redisOptions.isCluster()) {
            return readerOptions.configure(DataStructureItemReader.client(redisOptions.redisClusterClient()).poolConfig(redisOptions.poolConfig())).build();
        }
        return readerOptions.configure(DataStructureItemReader.client(redisOptions.redisClient()).poolConfig(redisOptions.poolConfig())).build();
    }

    protected DataStructureValueReader targetDataStructureValueReader() {
        if (targetRedisOptions.isCluster()) {
            return DataStructureValueReader.client(targetRedisOptions.redisClusterClient()).poolConfig(targetRedisOptions.poolConfig()).build();
        }
        return DataStructureValueReader.client(targetRedisOptions.redisClient()).poolConfig(targetRedisOptions.poolConfig()).build();
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy