io.streamthoughts.jikkou.kafka.change.topics.TopicChangeComputer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jikkou-provider-kafka Show documentation
Show all versions of jikkou-provider-kafka Show documentation
Integration between Apache Kafka and Jikkou
The newest version!
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright (c) The original authors
*
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package io.streamthoughts.jikkou.kafka.change.topics;
import static io.streamthoughts.jikkou.core.reconciler.Operation.DELETE;
import static io.streamthoughts.jikkou.kafka.change.topics.TopicChange.CONFIG_PREFIX;
import io.streamthoughts.jikkou.core.models.ConfigValue;
import io.streamthoughts.jikkou.core.models.change.GenericResourceChange;
import io.streamthoughts.jikkou.core.models.change.ResourceChange;
import io.streamthoughts.jikkou.core.models.change.ResourceChangeSpec;
import io.streamthoughts.jikkou.core.models.change.StateChange;
import io.streamthoughts.jikkou.core.reconciler.Operation;
import io.streamthoughts.jikkou.core.reconciler.change.ChangeComputer;
import io.streamthoughts.jikkou.core.reconciler.change.ChangeComputerBuilder;
import io.streamthoughts.jikkou.core.reconciler.change.ResourceChangeComputer;
import io.streamthoughts.jikkou.core.reconciler.change.ResourceChangeFactory;
import io.streamthoughts.jikkou.kafka.internals.KafkaTopics;
import io.streamthoughts.jikkou.kafka.models.V1KafkaTopic;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public final class TopicChangeComputer extends ResourceChangeComputer {
/**
* Creates a new {@link TopicChangeComputer} instance.
*/
public TopicChangeComputer() {
this(true);
}
/**
* Creates a new {@link TopicChangeComputer} instance.
*
* @param isConfigDeletionEnabled {@code true} to delete orphaned config entries.
*/
public TopicChangeComputer(boolean isConfigDeletionEnabled) {
super(ChangeComputerBuilder.KeyMapper.byName(), new TopicChangeFactory(isConfigDeletionEnabled));
}
public static final class TopicChangeFactory extends ResourceChangeFactory {
private final ChangeComputer configEntryChangeComputer;
public TopicChangeFactory(boolean isConfigDeletionEnabled) {
this.configEntryChangeComputer = getChangeComputerForConfig(isConfigDeletionEnabled);
}
@Override
public ResourceChange createChangeForCreate(String key, V1KafkaTopic after) {
List changes = new ArrayList<>();
changes.add(StateChange.create(TopicChange.PARTITIONS, getPartitionsOrDefault(after)));
changes.add(StateChange.create(TopicChange.REPLICAS, getReplicationFactorOrDefault(after)));
changes.addAll(getConfigChanges(null, after));
return buildResourceChange(after, Operation.CREATE, changes);
}
@Override
public ResourceChange createChangeForDelete(String key, V1KafkaTopic before) {
List changes = new ArrayList<>();
changes.add(StateChange.delete(TopicChange.PARTITIONS, getPartitionsOrDefault(before)));
changes.add(StateChange.delete(TopicChange.REPLICAS, getReplicationFactorOrDefault(before)));
changes.addAll(getConfigChanges(before, null));
return buildResourceChange(before, DELETE, changes);
}
@Override
public ResourceChange createChangeForUpdate(String key, V1KafkaTopic before, V1KafkaTopic after) {
StateChange partitions;
// Do not compute change when partition is equals to default.
if (getPartitionsOrDefault(after) == KafkaTopics.NO_NUM_PARTITIONS) {
partitions = StateChange.none(
TopicChange.PARTITIONS,
KafkaTopics.NO_NUM_PARTITIONS);
} else {
partitions = StateChange.with(
TopicChange.PARTITIONS,
getPartitionsOrDefault(before),
getPartitionsOrDefault(after)
);
}
StateChange replicas;
// Do not compute change when replication-factor is equals to default.
if (getReplicationFactorOrDefault(after) == KafkaTopics.NO_REPLICATION_FACTOR) {
replicas = StateChange.none(
TopicChange.REPLICAS,
KafkaTopics.NO_REPLICATION_FACTOR);
} else {
replicas = StateChange.with(
TopicChange.REPLICAS,
getReplicationFactorOrDefault(before),
getReplicationFactorOrDefault(after)
);
}
List configChanges = getConfigChanges(before, after);
boolean hasChanged = configChanges.stream()
.anyMatch(change -> change.getOp() != Operation.NONE);
var configOpType = hasChanged ? Operation.UPDATE : Operation.NONE;
var partitionOpType = partitions.getOp();
Operation op = List.of(partitionOpType, configOpType).contains(Operation.UPDATE) ?
Operation.UPDATE :
Operation.NONE;
List valueChanges = new ArrayList<>();
valueChanges.add(partitions);
valueChanges.add(replicas);
valueChanges.addAll(configChanges);
return buildResourceChange(before, op, valueChanges);
}
@NotNull
private List getConfigChanges(@Nullable V1KafkaTopic before,
@Nullable V1KafkaTopic after) {
List changes = configEntryChangeComputer.computeChanges(
Optional.ofNullable(before).map(o -> o.getSpec().getConfigs()).orElse(null),
Optional.ofNullable(after).map(o -> o.getSpec().getConfigs()).orElse(null)
);
return changes
.stream()
.map(change -> StateChange.builder()
.withName(CONFIG_PREFIX + change.getName())
.withOp(change.getOp())
.withBefore(change.getBefore())
.withAfter(change.getAfter())
.build()
)
.collect(Collectors.toList());
}
private static ResourceChange buildResourceChange(V1KafkaTopic resource,
Operation type,
List changes) {
return GenericResourceChange.builder(V1KafkaTopic.class)
.withMetadata(resource.getMetadata())
.withSpec(ResourceChangeSpec
.builder()
.withOperation(type)
.withChanges(changes)
.build()
)
.build();
}
/**
* @return the partition of the topic or default to ({@link KafkaTopics#NO_NUM_PARTITIONS})
*/
private int getPartitionsOrDefault(V1KafkaTopic resource) {
return Optional
.ofNullable(resource.getSpec())
.flatMap(spec -> Optional.ofNullable(spec.getPartitions()))
.orElse(KafkaTopics.NO_NUM_PARTITIONS);
}
/**
* @return the replication of the topic or default to ({@link KafkaTopics#NO_REPLICATION_FACTOR})
*/
private short getReplicationFactorOrDefault(V1KafkaTopic resource) {
return Optional
.ofNullable(resource.getSpec())
.flatMap(spec -> Optional.ofNullable(spec.getReplicas()))
.orElse(KafkaTopics.NO_REPLICATION_FACTOR);
}
private static ChangeComputer getChangeComputerForConfig(boolean deleteOrphans) {
return ChangeComputer
.builder()
.withDeleteOrphans(deleteOrphans)
.withKeyMapper(ConfigValue::name)
.withChangeFactory((key, before, after) -> {
Object beforeValue = Optional.ofNullable(before)
.map(ConfigValue::value)
.orElse(null);
Object afterValue = Optional.ofNullable(after)
.map(ConfigValue::value)
.orElse(null);
StateChange change = StateChange.with(key, beforeValue, afterValue);
return change.getOp() == DELETE && !before.isDeletable() ? Optional.empty() : Optional.of(change);
})
.build();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy