MINOR: clean up Streams assignment classes and tests (#8406)

First set of cleanup pushed to followup PR after KIP-441 Pt. 5. Main changes are:

1. Moved `RankedClient` and the static `buildClientRankingsByTask` to a new file
2. Moved `Movement` and the static `getMovements` to a new file (also renamed to `TaskMovement`)
3. Consolidated the many common variables throughout the assignment tests to the new `AssignmentTestUtils` 
4. New utility to generate comparable/predictable UUIDs for tests, and removed the generic from `TaskAssignor` and all related classes

Reviewers: John Roesler <vvcephei@apache.org>, Andrew Choi <a24choi@edu.uwaterloo.ca>
This commit is contained in:
A. Sophie Blee-Goldman 2020-04-03 11:53:51 -07:00 committed by GitHub
parent 62dcfa196e
commit 6e0d553350
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 1738 additions and 1527 deletions

View File

@ -208,9 +208,6 @@
<suppress checks="MethodLength"
files="RocksDBWindowStoreTest.java"/>
<suppress checks="MemberName"
files="StreamsPartitionAssignorTest.java"/>
<suppress checks="ClassDataAbstractionCoupling"
files=".*[/\\]streams[/\\].*test[/\\].*.java"/>

View File

@ -708,19 +708,19 @@ public class StreamsPartitionAssignor implements ConsumerPartitionAssignor, Conf
log.debug("Assigning tasks {} to clients {} with number of replicas {}",
allTasks, clientStates, numStandbyReplicas());
final TaskAssignor<UUID> taskAssignor;
final TaskAssignor taskAssignor;
if (highAvailabilityEnabled) {
if (lagComputationSuccessful) {
taskAssignor = new HighAvailabilityTaskAssignor<>(
taskAssignor = new HighAvailabilityTaskAssignor(
clientStates,
allTasks,
statefulTasks,
assignmentConfigs);
} else {
taskAssignor = new StickyTaskAssignor<>(clientStates, allTasks, statefulTasks, assignmentConfigs, true);
taskAssignor = new StickyTaskAssignor(clientStates, allTasks, statefulTasks, assignmentConfigs, true);
}
} else {
taskAssignor = new StickyTaskAssignor<>(clientStates, allTasks, statefulTasks, assignmentConfigs, false);
taskAssignor = new StickyTaskAssignor(clientStates, allTasks, statefulTasks, assignmentConfigs, false);
}
taskAssignor.assign();

View File

@ -16,16 +16,16 @@
*/
package org.apache.kafka.streams.processor.internals.assignment;
import org.apache.kafka.streams.processor.TaskId;
import java.util.UUID;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import org.apache.kafka.streams.processor.TaskId;
public interface BalancedAssignor<ID extends Comparable<? super ID>> {
public interface BalancedAssignor {
Map<ID, List<TaskId>> assign(final SortedSet<ID> clients,
Map<UUID, List<TaskId>> assign(final SortedSet<UUID> clients,
final SortedSet<TaskId> tasks,
final Map<ID, Integer> clientsToNumberOfStreamThreads,
final Map<UUID, Integer> clientsToNumberOfStreamThreads,
final int balanceFactor);
}

View File

@ -16,8 +16,7 @@
*/
package org.apache.kafka.streams.processor.internals.assignment;
import org.apache.kafka.streams.processor.TaskId;
import java.util.UUID;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
@ -25,27 +24,28 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import org.apache.kafka.streams.processor.TaskId;
public class DefaultBalancedAssignor<ID extends Comparable<? super ID>> implements BalancedAssignor<ID> {
public class DefaultBalancedAssignor implements BalancedAssignor {
@Override
public Map<ID, List<TaskId>> assign(final SortedSet<ID> clients,
public Map<UUID, List<TaskId>> assign(final SortedSet<UUID> clients,
final SortedSet<TaskId> tasks,
final Map<ID, Integer> clientsToNumberOfStreamThreads,
final Map<UUID, Integer> clientsToNumberOfStreamThreads,
final int balanceFactor) {
final Map<ID, List<TaskId>> assignment = new HashMap<>();
final Map<UUID, List<TaskId>> assignment = new HashMap<>();
clients.forEach(client -> assignment.put(client, new ArrayList<>()));
distributeTasksEvenlyOverClients(assignment, clients, tasks);
balanceTasksOverStreamThreads(assignment, clients, clientsToNumberOfStreamThreads, balanceFactor);
return assignment;
}
private void distributeTasksEvenlyOverClients(final Map<ID, List<TaskId>> assignment,
final SortedSet<ID> clients,
private void distributeTasksEvenlyOverClients(final Map<UUID, List<TaskId>> assignment,
final SortedSet<UUID> clients,
final SortedSet<TaskId> tasks) {
final LinkedList<TaskId> tasksToAssign = new LinkedList<>(tasks);
while (!tasksToAssign.isEmpty()) {
for (final ID client : clients) {
for (final UUID client : clients) {
final TaskId task = tasksToAssign.poll();
if (task == null) {
@ -56,16 +56,16 @@ public class DefaultBalancedAssignor<ID extends Comparable<? super ID>> implemen
}
}
private void balanceTasksOverStreamThreads(final Map<ID, List<TaskId>> assignment,
final SortedSet<ID> clients,
final Map<ID, Integer> clientsToNumberOfStreamThreads,
private void balanceTasksOverStreamThreads(final Map<UUID, List<TaskId>> assignment,
final SortedSet<UUID> clients,
final Map<UUID, Integer> clientsToNumberOfStreamThreads,
final int balanceFactor) {
boolean stop = false;
while (!stop) {
stop = true;
for (final ID sourceClient : clients) {
for (final UUID sourceClient : clients) {
final List<TaskId> sourceTasks = assignment.get(sourceClient);
for (final ID destinationClient : clients) {
for (final UUID destinationClient : clients) {
if (sourceClient.equals(destinationClient)) {
continue;
}

View File

@ -26,11 +26,11 @@ import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.UUID;
import org.apache.kafka.streams.processor.TaskId;
import org.apache.kafka.streams.processor.internals.Task;
import org.apache.kafka.streams.processor.internals.assignment.HighAvailabilityTaskAssignor.RankedClient;
public class DefaultStateConstrainedBalancedAssignor<ID extends Comparable<? super ID>> implements StateConstrainedBalancedAssignor<ID> {
public class DefaultStateConstrainedBalancedAssignor implements StateConstrainedBalancedAssignor {
/**
* This assignment algorithm guarantees that all task for which caught-up clients exist are assigned to one of the
@ -44,13 +44,13 @@ public class DefaultStateConstrainedBalancedAssignor<ID extends Comparable<? sup
* @return assignment
*/
@Override
public Map<ID, List<TaskId>> assign(final SortedMap<TaskId, SortedSet<RankedClient<ID>>> statefulTasksToRankedClients,
public Map<UUID, List<TaskId>> assign(final SortedMap<TaskId, SortedSet<RankedClient>> statefulTasksToRankedClients,
final int balanceFactor,
final Set<ID> clients,
final Map<ID, Integer> clientsToNumberOfStreamThreads) {
final Set<UUID> clients,
final Map<UUID, Integer> clientsToNumberOfStreamThreads) {
checkClientsAndNumberOfStreamThreads(clientsToNumberOfStreamThreads, clients);
final Map<ID, List<TaskId>> assignment = initAssignment(clients);
final Map<TaskId, List<ID>> tasksToCaughtUpClients = tasksToCaughtUpClients(statefulTasksToRankedClients);
final Map<UUID, List<TaskId>> assignment = initAssignment(clients);
final Map<TaskId, List<UUID>> tasksToCaughtUpClients = tasksToCaughtUpClients(statefulTasksToRankedClients);
assignTasksWithCaughtUpClients(
assignment,
tasksToCaughtUpClients,
@ -71,15 +71,15 @@ public class DefaultStateConstrainedBalancedAssignor<ID extends Comparable<? sup
return assignment;
}
private void checkClientsAndNumberOfStreamThreads(final Map<ID, Integer> clientsToNumberOfStreamThreads,
final Set<ID> clients) {
private void checkClientsAndNumberOfStreamThreads(final Map<UUID, Integer> clientsToNumberOfStreamThreads,
final Set<UUID> clients) {
if (clients.isEmpty()) {
throw new IllegalStateException("Set of clients must not be empty");
}
if (clientsToNumberOfStreamThreads.isEmpty()) {
throw new IllegalStateException("Map from clients to their number of stream threads must not be empty");
}
final Set<ID> copyOfClients = new HashSet<>(clients);
final Set<UUID> copyOfClients = new HashSet<>(clients);
copyOfClients.removeAll(clientsToNumberOfStreamThreads.keySet());
if (!copyOfClients.isEmpty()) {
throw new IllegalStateException(
@ -95,8 +95,8 @@ public class DefaultStateConstrainedBalancedAssignor<ID extends Comparable<? sup
* @param clients list of clients
* @return initialised assignment with empty lists
*/
private Map<ID, List<TaskId>> initAssignment(final Set<ID> clients) {
final Map<ID, List<TaskId>> assignment = new HashMap<>();
private Map<UUID, List<TaskId>> initAssignment(final Set<UUID> clients) {
final Map<UUID, List<TaskId>> assignment = new HashMap<>();
clients.forEach(client -> assignment.put(client, new ArrayList<>()));
return assignment;
}
@ -107,11 +107,11 @@ public class DefaultStateConstrainedBalancedAssignor<ID extends Comparable<? sup
* @param statefulTasksToRankedClients ranked clients map
* @return map from tasks with caught-up clients to the list of client candidates
*/
private Map<TaskId, List<ID>> tasksToCaughtUpClients(final SortedMap<TaskId, SortedSet<RankedClient<ID>>> statefulTasksToRankedClients) {
final Map<TaskId, List<ID>> taskToCaughtUpClients = new HashMap<>();
for (final SortedMap.Entry<TaskId, SortedSet<RankedClient<ID>>> taskToRankedClients : statefulTasksToRankedClients.entrySet()) {
final SortedSet<RankedClient<ID>> rankedClients = taskToRankedClients.getValue();
for (final RankedClient<ID> rankedClient : rankedClients) {
private Map<TaskId, List<UUID>> tasksToCaughtUpClients(final SortedMap<TaskId, SortedSet<RankedClient>> statefulTasksToRankedClients) {
final Map<TaskId, List<UUID>> taskToCaughtUpClients = new HashMap<>();
for (final SortedMap.Entry<TaskId, SortedSet<RankedClient>> taskToRankedClients : statefulTasksToRankedClients.entrySet()) {
final SortedSet<RankedClient> rankedClients = taskToRankedClients.getValue();
for (final RankedClient rankedClient : rankedClients) {
if (rankedClient.rank() == Task.LATEST_OFFSET || rankedClient.rank() == 0) {
final TaskId taskId = taskToRankedClients.getKey();
taskToCaughtUpClients.computeIfAbsent(taskId, ignored -> new ArrayList<>()).add(rankedClient.clientId());
@ -126,12 +126,12 @@ public class DefaultStateConstrainedBalancedAssignor<ID extends Comparable<? sup
/**
* Maps a task to the client that host the task according to the previous assignment.
*
* @return map from task IDs to clients hosting the corresponding task
* @return map from task UUIDs to clients hosting the corresponding task
*/
private Map<TaskId, ID> previouslyRunningTasksToPreviousClients(final Map<TaskId, SortedSet<RankedClient<ID>>> statefulTasksToRankedClients) {
final Map<TaskId, ID> tasksToPreviousClients = new HashMap<>();
for (final Map.Entry<TaskId, SortedSet<RankedClient<ID>>> taskToRankedClients : statefulTasksToRankedClients.entrySet()) {
final RankedClient<ID> topRankedClient = taskToRankedClients.getValue().first();
private Map<TaskId, UUID> previouslyRunningTasksToPreviousClients(final Map<TaskId, SortedSet<RankedClient>> statefulTasksToRankedClients) {
final Map<TaskId, UUID> tasksToPreviousClients = new HashMap<>();
for (final Map.Entry<TaskId, SortedSet<RankedClient>> taskToRankedClients : statefulTasksToRankedClients.entrySet()) {
final RankedClient topRankedClient = taskToRankedClients.getValue().first();
if (topRankedClient.rank() == Task.LATEST_OFFSET) {
tasksToPreviousClients.put(taskToRankedClients.getKey(), topRankedClient.clientId());
}
@ -142,13 +142,13 @@ public class DefaultStateConstrainedBalancedAssignor<ID extends Comparable<? sup
/**
* Assigns tasks for which one or more caught-up clients exist to one of the caught-up clients.
* @param assignment assignment
* @param tasksToCaughtUpClients map from task IDs to lists of caught-up clients
* @param tasksToCaughtUpClients map from task UUIDs to lists of caught-up clients
*/
private void assignTasksWithCaughtUpClients(final Map<ID, List<TaskId>> assignment,
final Map<TaskId, List<ID>> tasksToCaughtUpClients,
final Map<TaskId, SortedSet<RankedClient<ID>>> statefulTasksToRankedClients) {
private void assignTasksWithCaughtUpClients(final Map<UUID, List<TaskId>> assignment,
final Map<TaskId, List<UUID>> tasksToCaughtUpClients,
final Map<TaskId, SortedSet<RankedClient>> statefulTasksToRankedClients) {
// If a task was previously assigned to a client that is caught-up and still exists, give it back to the client
final Map<TaskId, ID> previouslyRunningTasksToPreviousClients =
final Map<TaskId, UUID> previouslyRunningTasksToPreviousClients =
previouslyRunningTasksToPreviousClients(statefulTasksToRankedClients);
previouslyRunningTasksToPreviousClients.forEach((task, client) -> assignment.get(client).add(task));
final List<TaskId> unassignedTasksWithCaughtUpClients = new ArrayList<>(tasksToCaughtUpClients.keySet());
@ -157,10 +157,10 @@ public class DefaultStateConstrainedBalancedAssignor<ID extends Comparable<? sup
// If a task's previous host client was not caught-up or no longer exists, assign it to the caught-up client
// with the least tasks
for (final TaskId taskId : unassignedTasksWithCaughtUpClients) {
final List<ID> caughtUpClients = tasksToCaughtUpClients.get(taskId);
ID clientWithLeastTasks = null;
final List<UUID> caughtUpClients = tasksToCaughtUpClients.get(taskId);
UUID clientWithLeastTasks = null;
int minTaskPerStreamThread = Integer.MAX_VALUE;
for (final ID client : caughtUpClients) {
for (final UUID client : caughtUpClients) {
final int assignedTasks = assignment.get(client).size();
if (minTaskPerStreamThread > assignedTasks) {
clientWithLeastTasks = client;
@ -175,22 +175,22 @@ public class DefaultStateConstrainedBalancedAssignor<ID extends Comparable<? sup
* Assigns tasks for which no caught-up clients exist.
* A task is assigned to one of the clients with the highest rank and the least tasks assigned.
* @param assignment assignment
* @param tasksToCaughtUpClients map from task IDs to lists of caught-up clients
* @param tasksToCaughtUpClients map from task UUIDs to lists of caught-up clients
* @param statefulTasksToRankedClients ranked clients map
*/
private void assignTasksWithoutCaughtUpClients(final Map<ID, List<TaskId>> assignment,
final Map<TaskId, List<ID>> tasksToCaughtUpClients,
final Map<TaskId, SortedSet<RankedClient<ID>>> statefulTasksToRankedClients) {
private void assignTasksWithoutCaughtUpClients(final Map<UUID, List<TaskId>> assignment,
final Map<TaskId, List<UUID>> tasksToCaughtUpClients,
final Map<TaskId, SortedSet<RankedClient>> statefulTasksToRankedClients) {
final SortedSet<TaskId> unassignedTasksWithoutCaughtUpClients = new TreeSet<>(statefulTasksToRankedClients.keySet());
unassignedTasksWithoutCaughtUpClients.removeAll(tasksToCaughtUpClients.keySet());
for (final TaskId taskId : unassignedTasksWithoutCaughtUpClients) {
final SortedSet<RankedClient<ID>> rankedClients = statefulTasksToRankedClients.get(taskId);
final SortedSet<RankedClient> rankedClients = statefulTasksToRankedClients.get(taskId);
final long topRank = rankedClients.first().rank();
int minTasksPerStreamThread = Integer.MAX_VALUE;
ID clientWithLeastTasks = rankedClients.first().clientId();
for (final RankedClient<ID> rankedClient : rankedClients) {
UUID clientWithLeastTasks = rankedClients.first().clientId();
for (final RankedClient rankedClient : rankedClients) {
if (rankedClient.rank() == topRank) {
final ID clientId = rankedClient.clientId();
final UUID clientId = rankedClient.clientId();
final int assignedTasks = assignment.get(clientId).size();
if (minTasksPerStreamThread > assignedTasks) {
clientWithLeastTasks = clientId;
@ -209,17 +209,17 @@ public class DefaultStateConstrainedBalancedAssignor<ID extends Comparable<? sup
* @param assignment assignment
* @param balanceFactor balance factor
* @param statefulTasksToRankedClients ranked clients map
* @param tasksToCaughtUpClients map from task IDs to lists of caught-up clients
* @param tasksToCaughtUpClients map from task UUIDs to lists of caught-up clients
* @param clientsToNumberOfStreamThreads map from clients to their number of stream threads
*/
private void balance(final Map<ID, List<TaskId>> assignment,
private void balance(final Map<UUID, List<TaskId>> assignment,
final int balanceFactor,
final Map<TaskId, SortedSet<RankedClient<ID>>> statefulTasksToRankedClients,
final Map<TaskId, List<ID>> tasksToCaughtUpClients,
final Map<ID, Integer> clientsToNumberOfStreamThreads) {
final List<ID> clients = new ArrayList<>(assignment.keySet());
final Map<TaskId, SortedSet<RankedClient>> statefulTasksToRankedClients,
final Map<TaskId, List<UUID>> tasksToCaughtUpClients,
final Map<UUID, Integer> clientsToNumberOfStreamThreads) {
final List<UUID> clients = new ArrayList<>(assignment.keySet());
Collections.sort(clients);
for (final ID sourceClientId : clients) {
for (final UUID sourceClientId : clients) {
final List<TaskId> sourceTasks = assignment.get(sourceClientId);
maybeMoveSourceTasksWithoutCaughtUpClients(
assignment,
@ -241,18 +241,18 @@ public class DefaultStateConstrainedBalancedAssignor<ID extends Comparable<? sup
}
}
private void maybeMoveSourceTasksWithoutCaughtUpClients(final Map<ID, List<TaskId>> assignment,
private void maybeMoveSourceTasksWithoutCaughtUpClients(final Map<UUID, List<TaskId>> assignment,
final int balanceFactor,
final Map<TaskId, SortedSet<RankedClient<ID>>> statefulTasksToRankedClients,
final Map<TaskId, List<ID>> tasksToCaughtUpClients,
final Map<ID, Integer> clientsToNumberOfStreamThreads,
final ID sourceClientId,
final Map<TaskId, SortedSet<RankedClient>> statefulTasksToRankedClients,
final Map<TaskId, List<UUID>> tasksToCaughtUpClients,
final Map<UUID, Integer> clientsToNumberOfStreamThreads,
final UUID sourceClientId,
final List<TaskId> sourceTasks) {
for (final TaskId task : assignedTasksWithoutCaughtUpClientsThatMightBeMoved(sourceTasks, tasksToCaughtUpClients)) {
final int assignedTasksPerStreamThreadAtSource =
sourceTasks.size() / clientsToNumberOfStreamThreads.get(sourceClientId);
for (final RankedClient<ID> clientAndRank : statefulTasksToRankedClients.get(task)) {
final ID destinationClientId = clientAndRank.clientId();
for (final RankedClient clientAndRank : statefulTasksToRankedClients.get(task)) {
final UUID destinationClientId = clientAndRank.clientId();
final List<TaskId> destination = assignment.get(destinationClientId);
final int assignedTasksPerStreamThreadAtDestination =
destination.size() / clientsToNumberOfStreamThreads.get(destinationClientId);
@ -265,16 +265,16 @@ public class DefaultStateConstrainedBalancedAssignor<ID extends Comparable<? sup
}
}
private void maybeMoveSourceTasksWithCaughtUpClients(final Map<ID, List<TaskId>> assignment,
private void maybeMoveSourceTasksWithCaughtUpClients(final Map<UUID, List<TaskId>> assignment,
final int balanceFactor,
final Map<TaskId, List<ID>> tasksToCaughtUpClients,
final Map<ID, Integer> clientsToNumberOfStreamThreads,
final ID sourceClientId,
final Map<TaskId, List<UUID>> tasksToCaughtUpClients,
final Map<UUID, Integer> clientsToNumberOfStreamThreads,
final UUID sourceClientId,
final List<TaskId> sourceTasks) {
for (final TaskId task : assignedTasksWithCaughtUpClientsThatMightBeMoved(sourceTasks, tasksToCaughtUpClients)) {
final int assignedTasksPerStreamThreadAtSource =
sourceTasks.size() / clientsToNumberOfStreamThreads.get(sourceClientId);
for (final ID destinationClientId : tasksToCaughtUpClients.get(task)) {
for (final UUID destinationClientId : tasksToCaughtUpClients.get(task)) {
final List<TaskId> destination = assignment.get(destinationClientId);
final int assignedTasksPerStreamThreadAtDestination =
destination.size() / clientsToNumberOfStreamThreads.get(destinationClientId);
@ -290,29 +290,29 @@ public class DefaultStateConstrainedBalancedAssignor<ID extends Comparable<? sup
/**
* Returns a sublist of tasks in the given list that does not have a caught-up client.
*
* @param tasks list of task IDs
* @param tasksToCaughtUpClients map from task IDs to lists of caught-up clients
* @return a list of task IDs that does not have a caught-up client
* @param tasks list of task UUIDs
* @param tasksToCaughtUpClients map from task UUIDs to lists of caught-up clients
* @return a list of task UUIDs that does not have a caught-up client
*/
private List<TaskId> assignedTasksWithoutCaughtUpClientsThatMightBeMoved(final List<TaskId> tasks,
final Map<TaskId, List<ID>> tasksToCaughtUpClients) {
final Map<TaskId, List<UUID>> tasksToCaughtUpClients) {
return assignedTasksThatMightBeMoved(tasks, tasksToCaughtUpClients, false);
}
/**
* Returns a sublist of tasks in the given list that have a caught-up client.
*
* @param tasks list of task IDs
* @param tasksToCaughtUpClients map from task IDs to lists of caught-up clients
* @return a list of task IDs that have a caught-up client
* @param tasks list of task UUIDs
* @param tasksToCaughtUpClients map from task UUIDs to lists of caught-up clients
* @return a list of task UUIDs that have a caught-up client
*/
private List<TaskId> assignedTasksWithCaughtUpClientsThatMightBeMoved(final List<TaskId> tasks,
final Map<TaskId, List<ID>> tasksToCaughtUpClients) {
final Map<TaskId, List<UUID>> tasksToCaughtUpClients) {
return assignedTasksThatMightBeMoved(tasks, tasksToCaughtUpClients, true);
}
private List<TaskId> assignedTasksThatMightBeMoved(final List<TaskId> tasks,
final Map<TaskId, List<ID>> tasksToCaughtUpClients,
final Map<TaskId, List<UUID>> tasksToCaughtUpClients,
final boolean isCaughtUp) {
final List<TaskId> tasksWithCaughtUpClients = new ArrayList<>();
for (int i = tasks.size() - 1; i >= 0; --i) {

View File

@ -17,7 +17,8 @@
package org.apache.kafka.streams.processor.internals.assignment;
import static java.util.Arrays.asList;
import static org.apache.kafka.streams.processor.internals.assignment.SubscriptionInfo.UNKNOWN_OFFSET_SUM;
import static org.apache.kafka.streams.processor.internals.assignment.RankedClient.buildClientRankingsByTask;
import static org.apache.kafka.streams.processor.internals.assignment.TaskMovement.getMovements;
import java.util.ArrayList;
import java.util.Collection;
@ -26,15 +27,13 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.PriorityQueue;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.UUID;
import java.util.stream.Collectors;
import org.apache.kafka.streams.processor.TaskId;
import org.apache.kafka.streams.processor.internals.Task;
import org.apache.kafka.streams.processor.internals.assignment.AssignorConfiguration.AssignmentConfigs;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -42,12 +41,12 @@ import org.slf4j.LoggerFactory;
import java.util.Map;
import java.util.Set;
public class HighAvailabilityTaskAssignor<ID extends Comparable<ID>> implements TaskAssignor<ID> {
public class HighAvailabilityTaskAssignor implements TaskAssignor {
private static final Logger log = LoggerFactory.getLogger(HighAvailabilityTaskAssignor.class);
private final Map<ID, ClientState> clientStates;
private final Map<ID, Integer> clientsToNumberOfThreads;
private final SortedSet<ID> sortedClients;
private final Map<UUID, ClientState> clientStates;
private final Map<UUID, Integer> clientsToNumberOfThreads;
private final SortedSet<UUID> sortedClients;
private final Set<TaskId> allTasks;
private final SortedSet<TaskId> statefulTasks;
@ -55,9 +54,9 @@ public class HighAvailabilityTaskAssignor<ID extends Comparable<ID>> implements
private final AssignmentConfigs configs;
private final SortedMap<TaskId, SortedSet<RankedClient<ID>>> statefulTasksToRankedCandidates;
private final SortedMap<TaskId, SortedSet<RankedClient>> statefulTasksToRankedCandidates;
public HighAvailabilityTaskAssignor(final Map<ID, ClientState> clientStates,
public HighAvailabilityTaskAssignor(final Map<UUID, ClientState> clientStates,
final Set<TaskId> allTasks,
final Set<TaskId> statefulTasks,
final AssignmentConfigs configs) {
@ -87,14 +86,14 @@ public class HighAvailabilityTaskAssignor<ID extends Comparable<ID>> implements
return false;
}
final Map<ID, List<TaskId>> warmupTaskAssignment = initializeEmptyTaskAssignmentMap();
final Map<ID, List<TaskId>> standbyTaskAssignment = initializeEmptyTaskAssignmentMap();
final Map<ID, List<TaskId>> statelessActiveTaskAssignment = initializeEmptyTaskAssignmentMap();
final Map<UUID, List<TaskId>> warmupTaskAssignment = initializeEmptyTaskAssignmentMap(sortedClients);
final Map<UUID, List<TaskId>> standbyTaskAssignment = initializeEmptyTaskAssignmentMap(sortedClients);
final Map<UUID, List<TaskId>> statelessActiveTaskAssignment = initializeEmptyTaskAssignmentMap(sortedClients);
// ---------------- Stateful Active Tasks ---------------- //
final Map<ID, List<TaskId>> statefulActiveTaskAssignment =
new DefaultStateConstrainedBalancedAssignor<ID>().assign(
final Map<UUID, List<TaskId>> statefulActiveTaskAssignment =
new DefaultStateConstrainedBalancedAssignor().assign(
statefulTasksToRankedCandidates,
configs.balanceFactor,
sortedClients,
@ -103,30 +102,32 @@ public class HighAvailabilityTaskAssignor<ID extends Comparable<ID>> implements
// ---------------- Warmup Replica Tasks ---------------- //
final Map<ID, List<TaskId>> balancedStatefulActiveTaskAssignment =
new DefaultBalancedAssignor<ID>().assign(
final Map<UUID, List<TaskId>> balancedStatefulActiveTaskAssignment =
new DefaultBalancedAssignor().assign(
sortedClients,
statefulTasks,
clientsToNumberOfThreads,
configs.balanceFactor);
final List<Movement<ID>> movements =
getMovements(statefulActiveTaskAssignment, balancedStatefulActiveTaskAssignment,
final List<TaskMovement> movements = getMovements(
statefulActiveTaskAssignment,
balancedStatefulActiveTaskAssignment,
configs.maxWarmupReplicas);
for (final Movement<ID> movement : movements) {
for (final TaskMovement movement : movements) {
warmupTaskAssignment.get(movement.destination).add(movement.task);
}
// ---------------- Standby Replica Tasks ---------------- //
final List<Map<ID, List<TaskId>>> allTaskAssignments = asList(
final List<Map<UUID, List<TaskId>>> allTaskAssignments = asList(
statefulActiveTaskAssignment,
warmupTaskAssignment,
standbyTaskAssignment,
statelessActiveTaskAssignment
);
final ValidClientsByTaskLoadQueue<ID> clientsByStandbyTaskLoad =
final ValidClientsByTaskLoadQueue<UUID> clientsByStandbyTaskLoad =
new ValidClientsByTaskLoadQueue<>(
configs.numStandbyReplicas,
getClientPriorityQueueByTaskLoad(allTaskAssignments),
@ -134,8 +135,8 @@ public class HighAvailabilityTaskAssignor<ID extends Comparable<ID>> implements
);
for (final TaskId task : statefulTasksToRankedCandidates.keySet()) {
final List<ID> clients = clientsByStandbyTaskLoad.poll(task);
for (final ID client : clients) {
final List<UUID> clients = clientsByStandbyTaskLoad.poll(task);
for (final UUID client : clients) {
standbyTaskAssignment.get(client).add(task);
}
clientsByStandbyTaskLoad.offer(clients);
@ -151,10 +152,10 @@ public class HighAvailabilityTaskAssignor<ID extends Comparable<ID>> implements
// ---------------- Stateless Active Tasks ---------------- //
final PriorityQueue<ID> statelessActiveTaskClientsQueue = getClientPriorityQueueByTaskLoad(allTaskAssignments);
final PriorityQueue<UUID> statelessActiveTaskClientsQueue = getClientPriorityQueueByTaskLoad(allTaskAssignments);
for (final TaskId task : statelessTasks) {
final ID client = statelessActiveTaskClientsQueue.poll();
final UUID client = statelessActiveTaskClientsQueue.poll();
statelessActiveTaskAssignment.get(client).add(task);
statelessActiveTaskClientsQueue.offer(client);
}
@ -169,47 +170,6 @@ public class HighAvailabilityTaskAssignor<ID extends Comparable<ID>> implements
return !movements.isEmpty();
}
/**
* Returns a list of the movements of tasks from statefulActiveTaskAssignment to balancedStatefulActiveTaskAssignment
* @param statefulActiveTaskAssignment the initial assignment, with source clients
* @param balancedStatefulActiveTaskAssignment the final assignment, with destination clients
*/
static <ID> List<Movement<ID>> getMovements(final Map<ID, List<TaskId>> statefulActiveTaskAssignment,
final Map<ID, List<TaskId>> balancedStatefulActiveTaskAssignment,
final int maxWarmupReplicas) {
if (statefulActiveTaskAssignment.size() != balancedStatefulActiveTaskAssignment.size()) {
throw new IllegalStateException("Tried to compute movements but assignments differ in size.");
}
final Map<TaskId, ID> taskToDestinationClient = new HashMap<>();
for (final Map.Entry<ID, List<TaskId>> clientEntry : balancedStatefulActiveTaskAssignment.entrySet()) {
final ID destination = clientEntry.getKey();
for (final TaskId task : clientEntry.getValue()) {
taskToDestinationClient.put(task, destination);
}
}
final List<Movement<ID>> movements = new LinkedList<>();
for (final Map.Entry<ID, List<TaskId>> sourceClientEntry : statefulActiveTaskAssignment.entrySet()) {
final ID source = sourceClientEntry.getKey();
for (final TaskId task : sourceClientEntry.getValue()) {
final ID destination = taskToDestinationClient.get(task);
if (destination == null) {
log.error("Task {} is assigned to client {} in initial assignment but has no owner in the final " +
"balanced assignment.", task, source);
throw new IllegalStateException("Found task in initial assignment that was not assigned in the final.");
} else if (!source.equals(destination)) {
movements.add(new Movement<>(task, source, destination));
if (movements.size() == maxWarmupReplicas) {
return movements;
}
}
}
}
return movements;
}
/**
* @return true iff all active tasks with caught-up client are assigned to one of them, and all tasks are assigned
*/
@ -221,8 +181,8 @@ public class HighAvailabilityTaskAssignor<ID extends Comparable<ID>> implements
new HashMap<>(statefulTasksToRankedCandidates.keySet().stream()
.collect(Collectors.toMap(task -> task, task -> configs.numStandbyReplicas)));
for (final Map.Entry<ID, ClientState> clientEntry : clientStates.entrySet()) {
final ID client = clientEntry.getKey();
for (final Map.Entry<UUID, ClientState> clientEntry : clientStates.entrySet()) {
final UUID client = clientEntry.getKey();
final ClientState state = clientEntry.getValue();
final Set<TaskId> prevActiveTasks = state.prevActiveTasks();
@ -253,13 +213,13 @@ public class HighAvailabilityTaskAssignor<ID extends Comparable<ID>> implements
/**
* @return true if this client is caught-up for this task, or the task has no caught-up clients
*/
boolean taskIsCaughtUpOnClient(final TaskId task, final ID client) {
boolean taskIsCaughtUpOnClient(final TaskId task, final UUID client) {
boolean hasNoCaughtUpClients = true;
final SortedSet<RankedClient<ID>> rankedClients = statefulTasksToRankedCandidates.get(task);
final SortedSet<RankedClient> rankedClients = statefulTasksToRankedCandidates.get(task);
if (rankedClients == null) {
return true;
}
for (final RankedClient<ID> rankedClient : rankedClients) {
for (final RankedClient rankedClient : rankedClients) {
if (rankedClient.rank() <= 0L) {
if (rankedClient.clientId().equals(client)) {
return true;
@ -276,46 +236,6 @@ public class HighAvailabilityTaskAssignor<ID extends Comparable<ID>> implements
return hasNoCaughtUpClients;
}
/**
* Rankings are computed as follows, with lower being more caught up:
* Rank -1: active running task
* Rank 0: standby or restoring task whose overall lag is within the acceptableRecoveryLag bounds
* Rank 1: tasks whose lag is unknown, eg because it was not encoded in an older version subscription.
* Since it may have been caught-up, we rank it higher than clients whom we know are not caught-up
* to give it priority without classifying it as caught-up and risking violating high availability
* Rank 1+: all other tasks are ranked according to their actual total lag
* @return Sorted set of all client candidates for each stateful task, ranked by their overall lag. Tasks are
*/
static <ID extends Comparable<ID>> SortedMap<TaskId, SortedSet<RankedClient<ID>>> buildClientRankingsByTask(final Set<TaskId> statefulTasks,
final Map<ID, ClientState> clientStates,
final long acceptableRecoveryLag) {
final SortedMap<TaskId, SortedSet<RankedClient<ID>>> statefulTasksToRankedCandidates = new TreeMap<>();
for (final TaskId task : statefulTasks) {
final SortedSet<RankedClient<ID>> rankedClientCandidates = new TreeSet<>();
statefulTasksToRankedCandidates.put(task, rankedClientCandidates);
for (final Map.Entry<ID, ClientState> clientEntry : clientStates.entrySet()) {
final ID clientId = clientEntry.getKey();
final long taskLag = clientEntry.getValue().lagFor(task);
final long clientRank;
if (taskLag == Task.LATEST_OFFSET) {
clientRank = Task.LATEST_OFFSET;
} else if (taskLag == UNKNOWN_OFFSET_SUM) {
clientRank = 1L;
} else if (taskLag <= acceptableRecoveryLag) {
clientRank = 0L;
} else {
clientRank = taskLag;
}
rankedClientCandidates.add(new RankedClient<>(clientId, clientRank));
}
}
log.trace("Computed statefulTasksToRankedCandidates map as {}", statefulTasksToRankedCandidates);
return statefulTasksToRankedCandidates;
}
/**
* Compute the balance factor as the difference in stateful active task count per thread between the most and
* least loaded clients
@ -357,21 +277,21 @@ public class HighAvailabilityTaskAssignor<ID extends Comparable<ID>> implements
}
}
private Map<ID, List<TaskId>> initializeEmptyTaskAssignmentMap() {
return sortedClients.stream().collect(Collectors.toMap(id -> id, id -> new ArrayList<>()));
private static Map<UUID, List<TaskId>> initializeEmptyTaskAssignmentMap(final Set<UUID> clients) {
return clients.stream().collect(Collectors.toMap(id -> id, id -> new ArrayList<>()));
}
private void assignActiveTasksToClients(final Map<ID, List<TaskId>> activeTasks) {
for (final Map.Entry<ID, ClientState> clientEntry : clientStates.entrySet()) {
final ID clientId = clientEntry.getKey();
private void assignActiveTasksToClients(final Map<UUID, List<TaskId>> activeTasks) {
for (final Map.Entry<UUID, ClientState> clientEntry : clientStates.entrySet()) {
final UUID clientId = clientEntry.getKey();
final ClientState state = clientEntry.getValue();
state.assignActiveTasks(activeTasks.get(clientId));
}
}
private void assignStandbyTasksToClients(final Map<ID, List<TaskId>> standbyTasks) {
for (final Map.Entry<ID, ClientState> clientEntry : clientStates.entrySet()) {
final ID clientId = clientEntry.getKey();
private void assignStandbyTasksToClients(final Map<UUID, List<TaskId>> standbyTasks) {
for (final Map.Entry<UUID, ClientState> clientEntry : clientStates.entrySet()) {
final UUID clientId = clientEntry.getKey();
final ClientState state = clientEntry.getValue();
state.assignStandbyTasks(standbyTasks.get(clientId));
}
@ -384,8 +304,8 @@ public class HighAvailabilityTaskAssignor<ID extends Comparable<ID>> implements
}
}
private PriorityQueue<ID> getClientPriorityQueueByTaskLoad(final List<Map<ID, List<TaskId>>> taskLoadsByClient) {
final PriorityQueue<ID> queue = new PriorityQueue<>(
private PriorityQueue<UUID> getClientPriorityQueueByTaskLoad(final List<Map<UUID, List<TaskId>>> taskLoadsByClient) {
final PriorityQueue<UUID> queue = new PriorityQueue<>(
(client, other) -> {
final int clientTasksPerThread = tasksPerThread(client, taskLoadsByClient);
final int otherTasksPerThread = tasksPerThread(other, taskLoadsByClient);
@ -400,102 +320,25 @@ public class HighAvailabilityTaskAssignor<ID extends Comparable<ID>> implements
return queue;
}
private int tasksPerThread(final ID client, final List<Map<ID, List<TaskId>>> taskLoadsByClient) {
private int tasksPerThread(final UUID client, final List<Map<UUID, List<TaskId>>> taskLoadsByClient) {
double numTasks = 0;
for (final Map<ID, List<TaskId>> assignment : taskLoadsByClient) {
for (final Map<UUID, List<TaskId>> assignment : taskLoadsByClient) {
numTasks += assignment.get(client).size();
}
return (int) Math.ceil(numTasks / clientsToNumberOfThreads.get(client));
}
static class RankedClient<ID extends Comparable<? super ID>> implements Comparable<RankedClient<ID>> {
private final ID clientId;
private final long rank;
RankedClient(final ID clientId, final long rank) {
this.clientId = clientId;
this.rank = rank;
}
ID clientId() {
return clientId;
}
long rank() {
return rank;
}
@Override
public int compareTo(final RankedClient<ID> clientIdAndLag) {
if (rank < clientIdAndLag.rank) {
return -1;
} else if (rank > clientIdAndLag.rank) {
return 1;
} else {
return clientId.compareTo(clientIdAndLag.clientId);
}
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final RankedClient<?> that = (RankedClient<?>) o;
return rank == that.rank && Objects.equals(clientId, that.clientId);
}
@Override
public int hashCode() {
return Objects.hash(clientId, rank);
}
}
static class Movement<ID> {
final TaskId task;
final ID source;
final ID destination;
Movement(final TaskId task, final ID source, final ID destination) {
this.task = task;
this.source = source;
this.destination = destination;
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final Movement<?> movement = (Movement<?>) o;
return Objects.equals(task, movement.task) &&
Objects.equals(source, movement.source) &&
Objects.equals(destination, movement.destination);
}
@Override
public int hashCode() {
return Objects.hash(task, source, destination);
}
}
/**
* Wraps a priority queue of clients and returns the next valid candidate(s) based on the current task assignment
*/
static class ValidClientsByTaskLoadQueue<ID> {
static class ValidClientsByTaskLoadQueue<UUID> {
private final int numClientsPerTask;
private final PriorityQueue<ID> clientsByTaskLoad;
private final List<Map<ID, List<TaskId>>> allStatefulTaskAssignments;
private final PriorityQueue<UUID> clientsByTaskLoad;
private final List<Map<UUID, List<TaskId>>> allStatefulTaskAssignments;
ValidClientsByTaskLoadQueue(final int numClientsPerTask,
final PriorityQueue<ID> clientsByTaskLoad,
final List<Map<ID, List<TaskId>>> allStatefulTaskAssignments) {
final PriorityQueue<UUID> clientsByTaskLoad,
final List<Map<UUID, List<TaskId>>> allStatefulTaskAssignments) {
this.numClientsPerTask = numClientsPerTask;
this.clientsByTaskLoad = clientsByTaskLoad;
this.allStatefulTaskAssignments = allStatefulTaskAssignments;
@ -505,11 +348,11 @@ public class HighAvailabilityTaskAssignor<ID extends Comparable<ID>> implements
* @return the next N <= {@code numClientsPerTask} clients in the underlying priority queue that are valid
* candidates for the given task (ie do not already have any version of this task assigned)
*/
List<ID> poll(final TaskId task) {
final List<ID> nextLeastLoadedValidClients = new LinkedList<>();
final Set<ID> invalidPolledClients = new HashSet<>();
List<UUID> poll(final TaskId task) {
final List<UUID> nextLeastLoadedValidClients = new LinkedList<>();
final Set<UUID> invalidPolledClients = new HashSet<>();
while (nextLeastLoadedValidClients.size() < numClientsPerTask) {
ID candidateClient;
UUID candidateClient;
while (true) {
candidateClient = clientsByTaskLoad.poll();
if (candidateClient == null) {
@ -529,12 +372,12 @@ public class HighAvailabilityTaskAssignor<ID extends Comparable<ID>> implements
return nextLeastLoadedValidClients;
}
void offer(final Collection<ID> clients) {
void offer(final Collection<UUID> clients) {
returnPolledClientsToQueue(clients);
}
private boolean canBeAssignedToClient(final TaskId task, final ID client) {
for (final Map<ID, List<TaskId>> taskAssignment : allStatefulTaskAssignments) {
private boolean canBeAssignedToClient(final TaskId task, final UUID client) {
for (final Map<UUID, List<TaskId>> taskAssignment : allStatefulTaskAssignments) {
if (taskAssignment.get(client).contains(task)) {
return false;
}
@ -542,8 +385,8 @@ public class HighAvailabilityTaskAssignor<ID extends Comparable<ID>> implements
return true;
}
private void returnPolledClientsToQueue(final Collection<ID> polledClients) {
for (final ID client : polledClients) {
private void returnPolledClientsToQueue(final Collection<UUID> polledClients) {
for (final UUID client : polledClients) {
clientsByTaskLoad.offer(client);
}
}

View File

@ -0,0 +1,120 @@
/*
* 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.kafka.streams.processor.internals.assignment;
import static org.apache.kafka.streams.processor.internals.assignment.SubscriptionInfo.UNKNOWN_OFFSET_SUM;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.UUID;
import org.apache.kafka.streams.processor.TaskId;
import org.apache.kafka.streams.processor.internals.Task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RankedClient implements Comparable<RankedClient> {
private static final Logger log = LoggerFactory.getLogger(RankedClient.class);
private final UUID clientId;
private final long rank;
RankedClient(final UUID clientId, final long rank) {
this.clientId = clientId;
this.rank = rank;
}
UUID clientId() {
return clientId;
}
long rank() {
return rank;
}
@Override
public int compareTo(final RankedClient clientIdAndLag) {
if (rank < clientIdAndLag.rank) {
return -1;
} else if (rank > clientIdAndLag.rank) {
return 1;
} else {
return clientId.compareTo(clientIdAndLag.clientId);
}
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final RankedClient that = (RankedClient) o;
return rank == that.rank && Objects.equals(clientId, that.clientId);
}
@Override
public int hashCode() {
return Objects.hash(clientId, rank);
}
/**
* Rankings are computed as follows, with lower being more caught up:
* Rank -1: active running task
* Rank 0: standby or restoring task whose overall lag is within the acceptableRecoveryLag bounds
* Rank 1: tasks whose lag is unknown, eg because it was not encoded in an older version subscription.
* Since it may have been caught-up, we rank it higher than clients whom we know are not caught-up
* to give it priority without classifying it as caught-up and risking violating high availability
* Rank 1+: all other tasks are ranked according to their actual total lag
* @return Sorted set of all client candidates for each stateful task, ranked by their overall lag. Tasks are
*/
static SortedMap<TaskId, SortedSet<RankedClient>> buildClientRankingsByTask(final Set<TaskId> statefulTasks,
final Map<UUID, ClientState> clientStates,
final long acceptableRecoveryLag) {
final SortedMap<TaskId, SortedSet<RankedClient>> statefulTasksToRankedCandidates = new TreeMap<>();
for (final TaskId task : statefulTasks) {
final SortedSet<RankedClient> rankedClientCandidates = new TreeSet<>();
statefulTasksToRankedCandidates.put(task, rankedClientCandidates);
for (final Map.Entry<UUID, ClientState> clientEntry : clientStates.entrySet()) {
final UUID clientId = clientEntry.getKey();
final long taskLag = clientEntry.getValue().lagFor(task);
final long clientRank;
if (taskLag == Task.LATEST_OFFSET) {
clientRank = Task.LATEST_OFFSET;
} else if (taskLag == UNKNOWN_OFFSET_SUM) {
clientRank = 1L;
} else if (taskLag <= acceptableRecoveryLag) {
clientRank = 0L;
} else {
clientRank = taskLag;
}
rankedClientCandidates.add(new RankedClient(clientId, clientRank));
}
}
log.trace("Computed statefulTasksToRankedCandidates map as {}", statefulTasksToRankedCandidates);
return statefulTasksToRankedCandidates;
}
}

View File

@ -16,6 +16,7 @@
*/
package org.apache.kafka.streams.processor.internals.assignment;
import java.util.UUID;
import org.apache.kafka.streams.processor.TaskId;
import java.util.List;
@ -23,12 +24,11 @@ import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import org.apache.kafka.streams.processor.internals.assignment.HighAvailabilityTaskAssignor.RankedClient;
public interface StateConstrainedBalancedAssignor<ID extends Comparable<? super ID>> {
public interface StateConstrainedBalancedAssignor {
Map<ID, List<TaskId>> assign(final SortedMap<TaskId, SortedSet<RankedClient<ID>>> statefulTasksToRankedClients,
Map<UUID, List<TaskId>> assign(final SortedMap<TaskId, SortedSet<RankedClient>> statefulTasksToRankedClients,
final int balanceFactor,
final Set<ID> clients,
final Map<ID, Integer> clientsToNumberOfStreamThreads);
final Set<UUID> clients,
final Map<UUID, Integer> clientsToNumberOfStreamThreads);
}

View File

@ -16,6 +16,7 @@
*/
package org.apache.kafka.streams.processor.internals.assignment;
import java.util.UUID;
import org.apache.kafka.streams.processor.TaskId;
import org.apache.kafka.streams.processor.internals.assignment.AssignorConfiguration.AssignmentConfigs;
import org.slf4j.Logger;
@ -32,20 +33,20 @@ import java.util.Map;
import java.util.Objects;
import java.util.Set;
public class StickyTaskAssignor<ID> implements TaskAssignor<ID> {
public class StickyTaskAssignor implements TaskAssignor {
private static final Logger log = LoggerFactory.getLogger(StickyTaskAssignor.class);
private final Map<ID, ClientState> clients;
private final Map<UUID, ClientState> clients;
private final Set<TaskId> allTaskIds;
private final Set<TaskId> standbyTaskIds;
private final Map<TaskId, ID> previousActiveTaskAssignment = new HashMap<>();
private final Map<TaskId, Set<ID>> previousStandbyTaskAssignment = new HashMap<>();
private final Map<TaskId, UUID> previousActiveTaskAssignment = new HashMap<>();
private final Map<TaskId, Set<UUID>> previousStandbyTaskAssignment = new HashMap<>();
private final TaskPairs taskPairs;
private final int numStandbyReplicas;
private final boolean mustPreserveActiveTaskAssignment;
public StickyTaskAssignor(final Map<ID, ClientState> clients,
public StickyTaskAssignor(final Map<UUID, ClientState> clients,
final Set<TaskId> allTaskIds,
final Set<TaskId> standbyTaskIds,
final AssignmentConfigs configs,
@ -71,7 +72,7 @@ public class StickyTaskAssignor<ID> implements TaskAssignor<ID> {
private void assignStandby(final int numStandbyReplicas) {
for (final TaskId taskId : standbyTaskIds) {
for (int i = 0; i < numStandbyReplicas; i++) {
final Set<ID> ids = findClientsWithoutAssignedTask(taskId);
final Set<UUID> ids = findClientsWithoutAssignedTask(taskId);
if (ids.isEmpty()) {
log.warn("Unable to assign {} of {} standby tasks for task [{}]. " +
"There is not enough available capacity. You should " +
@ -93,7 +94,7 @@ public class StickyTaskAssignor<ID> implements TaskAssignor<ID> {
// first try and re-assign existing active tasks to clients that previously had
// the same active task
for (final Map.Entry<TaskId, ID> entry : previousActiveTaskAssignment.entrySet()) {
for (final Map.Entry<TaskId, UUID> entry : previousActiveTaskAssignment.entrySet()) {
final TaskId taskId = entry.getKey();
if (allTaskIds.contains(taskId)) {
final ClientState client = clients.get(entry.getValue());
@ -110,9 +111,9 @@ public class StickyTaskAssignor<ID> implements TaskAssignor<ID> {
// have seen the task.
for (final Iterator<TaskId> iterator = unassigned.iterator(); iterator.hasNext(); ) {
final TaskId taskId = iterator.next();
final Set<ID> clientIds = previousStandbyTaskAssignment.get(taskId);
final Set<UUID> clientIds = previousStandbyTaskAssignment.get(taskId);
if (clientIds != null) {
for (final ID clientId : clientIds) {
for (final UUID clientId : clientIds) {
final ClientState client = clients.get(clientId);
if (client.hasUnfulfilledQuota(tasksPerThread)) {
assignTaskToClient(assigned, taskId, client);
@ -131,7 +132,7 @@ public class StickyTaskAssignor<ID> implements TaskAssignor<ID> {
}
}
private void allocateTaskWithClientCandidates(final TaskId taskId, final Set<ID> clientsWithin, final boolean active) {
private void allocateTaskWithClientCandidates(final TaskId taskId, final Set<UUID> clientsWithin, final boolean active) {
final ClientState client = findClient(taskId, clientsWithin);
taskPairs.addPairs(taskId, client.assignedTasks());
if (active) {
@ -147,9 +148,9 @@ public class StickyTaskAssignor<ID> implements TaskAssignor<ID> {
assigned.add(taskId);
}
private Set<ID> findClientsWithoutAssignedTask(final TaskId taskId) {
final Set<ID> clientIds = new HashSet<>();
for (final Map.Entry<ID, ClientState> client : clients.entrySet()) {
private Set<UUID> findClientsWithoutAssignedTask(final TaskId taskId) {
final Set<UUID> clientIds = new HashSet<>();
for (final Map.Entry<UUID, ClientState> client : clients.entrySet()) {
if (!client.getValue().hasAssignedTask(taskId)) {
clientIds.add(client.getKey());
}
@ -158,7 +159,7 @@ public class StickyTaskAssignor<ID> implements TaskAssignor<ID> {
}
private ClientState findClient(final TaskId taskId, final Set<ID> clientsWithin) {
private ClientState findClient(final TaskId taskId, final Set<UUID> clientsWithin) {
// optimize the case where there is only 1 id to search within.
if (clientsWithin.size() == 1) {
@ -194,25 +195,25 @@ public class StickyTaskAssignor<ID> implements TaskAssignor<ID> {
return false;
}
private ClientState findClientsWithPreviousAssignedTask(final TaskId taskId, final Set<ID> clientsWithin) {
final ID previous = previousActiveTaskAssignment.get(taskId);
private ClientState findClientsWithPreviousAssignedTask(final TaskId taskId, final Set<UUID> clientsWithin) {
final UUID previous = previousActiveTaskAssignment.get(taskId);
if (previous != null && clientsWithin.contains(previous)) {
return clients.get(previous);
}
return findLeastLoadedClientWithPreviousStandByTask(taskId, clientsWithin);
}
private ClientState findLeastLoadedClientWithPreviousStandByTask(final TaskId taskId, final Set<ID> clientsWithin) {
final Set<ID> ids = previousStandbyTaskAssignment.get(taskId);
private ClientState findLeastLoadedClientWithPreviousStandByTask(final TaskId taskId, final Set<UUID> clientsWithin) {
final Set<UUID> ids = previousStandbyTaskAssignment.get(taskId);
if (ids == null) {
return null;
}
final HashSet<ID> constrainTo = new HashSet<>(ids);
final HashSet<UUID> constrainTo = new HashSet<>(ids);
constrainTo.retainAll(clientsWithin);
return leastLoaded(taskId, constrainTo);
}
private ClientState leastLoaded(final TaskId taskId, final Set<ID> clientIds) {
private ClientState leastLoaded(final TaskId taskId, final Set<UUID> clientIds) {
final ClientState leastLoaded = findLeastLoaded(taskId, clientIds, true);
if (leastLoaded == null) {
return findLeastLoaded(taskId, clientIds, false);
@ -221,10 +222,10 @@ public class StickyTaskAssignor<ID> implements TaskAssignor<ID> {
}
private ClientState findLeastLoaded(final TaskId taskId,
final Set<ID> clientIds,
final Set<UUID> clientIds,
final boolean checkTaskPairs) {
ClientState leastLoaded = null;
for (final ID id : clientIds) {
for (final UUID id : clientIds) {
final ClientState client = clients.get(id);
if (client.assignedTaskCount() == 0) {
return client;
@ -243,8 +244,8 @@ public class StickyTaskAssignor<ID> implements TaskAssignor<ID> {
}
private void mapPreviousTaskAssignment(final Map<ID, ClientState> clients) {
for (final Map.Entry<ID, ClientState> clientState : clients.entrySet()) {
private void mapPreviousTaskAssignment(final Map<UUID, ClientState> clients) {
for (final Map.Entry<UUID, ClientState> clientState : clients.entrySet()) {
for (final TaskId activeTask : clientState.getValue().prevActiveTasks()) {
previousActiveTaskAssignment.put(activeTask, clientState.getKey());
}

View File

@ -16,7 +16,7 @@
*/
package org.apache.kafka.streams.processor.internals.assignment;
public interface TaskAssignor<ID> {
public interface TaskAssignor {
/**
* @return whether the generated assignment requires a followup rebalance to satisfy all conditions
*/

View File

@ -0,0 +1,101 @@
/*
* 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.kafka.streams.processor.internals.assignment;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import org.apache.kafka.streams.processor.TaskId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TaskMovement {
private static final Logger log = LoggerFactory.getLogger(TaskMovement.class);
final TaskId task;
final UUID source;
final UUID destination;
TaskMovement(final TaskId task, final UUID source, final UUID destination) {
this.task = task;
this.source = source;
this.destination = destination;
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final TaskMovement movement = (TaskMovement) o;
return Objects.equals(task, movement.task) &&
Objects.equals(source, movement.source) &&
Objects.equals(destination, movement.destination);
}
@Override
public int hashCode() {
return Objects.hash(task, source, destination);
}
/**
* Returns a list of the movements of tasks from statefulActiveTaskAssignment to balancedStatefulActiveTaskAssignment
* @param statefulActiveTaskAssignment the initial assignment, with source clients
* @param balancedStatefulActiveTaskAssignment the final assignment, with destination clients
*/
static List<TaskMovement> getMovements(final Map<UUID, List<TaskId>> statefulActiveTaskAssignment,
final Map<UUID, List<TaskId>> balancedStatefulActiveTaskAssignment,
final int maxWarmupReplicas) {
if (statefulActiveTaskAssignment.size() != balancedStatefulActiveTaskAssignment.size()) {
throw new IllegalStateException("Tried to compute movements but assignments differ in size.");
}
final Map<TaskId, UUID> taskToDestinationClient = new HashMap<>();
for (final Map.Entry<UUID, List<TaskId>> clientEntry : balancedStatefulActiveTaskAssignment.entrySet()) {
final UUID destination = clientEntry.getKey();
for (final TaskId task : clientEntry.getValue()) {
taskToDestinationClient.put(task, destination);
}
}
final List<TaskMovement> movements = new LinkedList<>();
for (final Map.Entry<UUID, List<TaskId>> sourceClientEntry : statefulActiveTaskAssignment.entrySet()) {
final UUID source = sourceClientEntry.getKey();
for (final TaskId task : sourceClientEntry.getValue()) {
final UUID destination = taskToDestinationClient.get(task);
if (destination == null) {
log.error("Task {} is assigned to client {} in initial assignment but has no owner in the final " +
"balanced assignment.", task, source);
throw new IllegalStateException("Found task in initial assignment that was not assigned in the final.");
} else if (!source.equals(destination)) {
movements.add(new TaskMovement(task, source, destination));
if (movements.size() == maxWarmupReplicas) {
return movements;
}
}
}
}
return movements;
}
}

View File

@ -83,6 +83,23 @@ import static java.util.Collections.singletonList;
import static org.apache.kafka.common.utils.Utils.mkEntry;
import static org.apache.kafka.common.utils.Utils.mkMap;
import static org.apache.kafka.common.utils.Utils.mkSet;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.EMPTY_TASKS;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_2_1;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_2_2;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_2_3;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.UUID_1;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.UUID_2;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.EMPTY_CHANGELOG_END_OFFSETS;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.EMPTY_TASK_OFFSET_SUMS;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_0_0;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_0_1;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_0_2;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_0_3;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_1_0;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_1_1;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_1_2;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_1_3;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_2_0;
import static org.apache.kafka.streams.processor.internals.assignment.StreamsAssignmentProtocolVersions.LATEST_SUPPORTED_VERSION;
import static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.expect;
@ -105,6 +122,8 @@ public class StreamsPartitionAssignorTest {
private static final String CONSUMER_3 = "consumer3";
private static final String CONSUMER_4 = "consumer4";
private final Set<String> allTopics = mkSet("topic1", "topic2");
private final TopicPartition t1p0 = new TopicPartition("topic1", 0);
private final TopicPartition t1p1 = new TopicPartition("topic1", 1);
private final TopicPartition t1p2 = new TopicPartition("topic1", 2);
@ -122,38 +141,23 @@ public class StreamsPartitionAssignorTest {
private final TopicPartition t4p2 = new TopicPartition("topic4", 2);
private final TopicPartition t4p3 = new TopicPartition("topic4", 3);
private final TaskId task0_0 = new TaskId(0, 0);
private final TaskId task0_1 = new TaskId(0, 1);
private final TaskId task0_2 = new TaskId(0, 2);
private final TaskId task0_3 = new TaskId(0, 3);
private final TaskId task1_0 = new TaskId(1, 0);
private final TaskId task1_1 = new TaskId(1, 1);
private final TaskId task1_2 = new TaskId(1, 2);
private final TaskId task1_3 = new TaskId(1, 3);
private final TaskId task2_0 = new TaskId(2, 0);
private final TaskId task2_1 = new TaskId(2, 1);
private final TaskId task2_2 = new TaskId(2, 2);
private final TaskId task2_3 = new TaskId(2, 3);
private final Map<TaskId, Set<TopicPartition>> partitionsForTask = mkMap(
mkEntry(task0_0, mkSet(t1p0, t2p0)),
mkEntry(task0_1, mkSet(t1p1, t2p1)),
mkEntry(task0_2, mkSet(t1p2, t2p2)),
mkEntry(task0_3, mkSet(t1p3, t2p3)),
mkEntry(TASK_0_0, mkSet(t1p0, t2p0)),
mkEntry(TASK_0_1, mkSet(t1p1, t2p1)),
mkEntry(TASK_0_2, mkSet(t1p2, t2p2)),
mkEntry(TASK_0_3, mkSet(t1p3, t2p3)),
mkEntry(task1_0, mkSet(t3p0)),
mkEntry(task1_1, mkSet(t3p1)),
mkEntry(task1_2, mkSet(t3p2)),
mkEntry(task1_3, mkSet(t3p3)),
mkEntry(TASK_1_0, mkSet(t3p0)),
mkEntry(TASK_1_1, mkSet(t3p1)),
mkEntry(TASK_1_2, mkSet(t3p2)),
mkEntry(TASK_1_3, mkSet(t3p3)),
mkEntry(task2_0, mkSet(t4p0)),
mkEntry(task2_1, mkSet(t4p1)),
mkEntry(task2_2, mkSet(t4p2)),
mkEntry(task2_3, mkSet(t4p3))
mkEntry(TASK_2_0, mkSet(t4p0)),
mkEntry(TASK_2_1, mkSet(t4p1)),
mkEntry(TASK_2_2, mkSet(t4p2)),
mkEntry(TASK_2_3, mkSet(t4p3))
);
private final Set<String> allTopics = mkSet("topic1", "topic2");
private final List<PartitionInfo> infos = asList(
new PartitionInfo("topic1", 0, Node.noNode(), new Node[0], new Node[0]),
new PartitionInfo("topic1", 1, Node.noNode(), new Node[0], new Node[0]),
@ -167,14 +171,7 @@ public class StreamsPartitionAssignorTest {
new PartitionInfo("topic3", 3, Node.noNode(), new Node[0], new Node[0])
);
private final Set<TaskId> emptyTasks = emptySet();
private final Map<TaskId, Long> emptyTaskOffsetSums = emptyMap();
private final Map<TopicPartition, Long> emptyChangelogEndOffsets = new HashMap<>();
private final UUID uuid1 = UUID.randomUUID();
private final UUID uuid2 = UUID.randomUUID();
private final SubscriptionInfo defaultSubscriptionInfo = getInfo(uuid1, emptyTasks, emptyTasks);
private final SubscriptionInfo defaultSubscriptionInfo = getInfo(UUID_1, EMPTY_TASKS, EMPTY_TASKS);
private final Cluster metadata = new Cluster(
"cluster",
@ -232,13 +229,13 @@ public class StreamsPartitionAssignorTest {
}
private void createDefaultMockTaskManager() {
createMockTaskManager(emptyTaskOffsetSums, uuid1);
createMockTaskManager(EMPTY_TASK_OFFSET_SUMS, UUID_1);
}
// Useful for tests that don't care about the task offset sums
private void createMockTaskManager(final Set<TaskId> activeTasks,
final Set<TaskId> standbyTasks) {
createMockTaskManager(getTaskOffsetSums(activeTasks, standbyTasks), uuid1);
createMockTaskManager(getTaskOffsetSums(activeTasks, standbyTasks), UUID_1);
}
private void createMockTaskManager(final Map<TaskId, Long> taskOffsetSums,
@ -290,7 +287,7 @@ public class StreamsPartitionAssignorTest {
public StreamsPartitionAssignorTest(final boolean highAvailabilityEnabled) {
this.highAvailabilityEnabled = highAvailabilityEnabled;
createMockAdminClient(emptyChangelogEndOffsets);
createMockAdminClient(EMPTY_CHANGELOG_END_OFFSETS);
}
@Test
@ -315,12 +312,13 @@ public class StreamsPartitionAssignorTest {
public void shouldProduceStickyAndBalancedAssignmentWhenNothingChanges() {
configureDefault();
final ClientState state = new ClientState();
final List<TaskId> allTasks = asList(task0_0, task0_1, task0_2, task0_3, task1_0, task1_1, task1_2, task1_3);
final List<TaskId> allTasks = asList(TASK_0_0, TASK_0_1, TASK_0_2, TASK_0_3, TASK_1_0, TASK_1_1, TASK_1_2,
TASK_1_3);
final Map<String, List<TaskId>> previousAssignment = mkMap(
mkEntry(CONSUMER_1, asList(task0_0, task1_1, task1_3)),
mkEntry(CONSUMER_2, asList(task0_3, task1_0)),
mkEntry(CONSUMER_3, asList(task0_1, task0_2, task1_2))
mkEntry(CONSUMER_1, asList(TASK_0_0, TASK_1_1, TASK_1_3)),
mkEntry(CONSUMER_2, asList(TASK_0_3, TASK_1_0)),
mkEntry(CONSUMER_3, asList(TASK_0_1, TASK_0_2, TASK_1_2))
);
for (final Map.Entry<String, List<TaskId>> entry : previousAssignment.entrySet()) {
@ -348,12 +346,13 @@ public class StreamsPartitionAssignorTest {
configureDefault();
final ClientState state = new ClientState();
final Set<TaskId> allTasks = mkSet(task0_0, task0_1, task0_2, task0_3, task1_0, task1_1, task1_2, task1_3);
final Set<TaskId> allTasks = mkSet(TASK_0_0, TASK_0_1, TASK_0_2, TASK_0_3, TASK_1_0, TASK_1_1, TASK_1_2,
TASK_1_3);
final Map<String, List<TaskId>> previousAssignment = mkMap(
mkEntry(CONSUMER_1, new ArrayList<>(asList(task0_0, task1_1, task1_3))),
mkEntry(CONSUMER_2, new ArrayList<>(asList(task0_3, task1_0))),
mkEntry(CONSUMER_3, new ArrayList<>(asList(task0_1, task0_2, task1_2)))
mkEntry(CONSUMER_1, new ArrayList<>(asList(TASK_0_0, TASK_1_1, TASK_1_3))),
mkEntry(CONSUMER_2, new ArrayList<>(asList(TASK_0_3, TASK_1_0))),
mkEntry(CONSUMER_3, new ArrayList<>(asList(TASK_0_1, TASK_0_2, TASK_1_2)))
);
for (final Map.Entry<String, List<TaskId>> entry : previousAssignment.entrySet()) {
@ -365,7 +364,7 @@ public class StreamsPartitionAssignorTest {
final Set<String> consumers = mkSet(CONSUMER_1, CONSUMER_2, CONSUMER_3);
// We should be able to add a new task without sacrificing stickyness
final TaskId newTask = task2_0;
final TaskId newTask = TASK_2_0;
allTasks.add(newTask);
state.assignActiveTasks(allTasks);
@ -381,12 +380,13 @@ public class StreamsPartitionAssignorTest {
configureDefault();
final ClientState state = new ClientState();
final List<TaskId> allTasks = asList(task0_0, task0_1, task0_2, task0_3, task1_0, task1_1, task1_2, task1_3);
final List<TaskId> allTasks = asList(TASK_0_0, TASK_0_1, TASK_0_2, TASK_0_3, TASK_1_0, TASK_1_1, TASK_1_2,
TASK_1_3);
final Map<String, List<TaskId>> previousAssignment = mkMap(
mkEntry(CONSUMER_1, asList(task0_0, task1_1, task1_3)),
mkEntry(CONSUMER_2, asList(task0_3, task1_0)),
mkEntry(CONSUMER_3, asList(task0_1, task0_2, task1_2))
mkEntry(CONSUMER_1, asList(TASK_0_0, TASK_1_1, TASK_1_3)),
mkEntry(CONSUMER_2, asList(TASK_0_3, TASK_1_0)),
mkEntry(CONSUMER_3, asList(TASK_0_1, TASK_0_2, TASK_1_2))
);
for (final Map.Entry<String, List<TaskId>> entry : previousAssignment.entrySet()) {
@ -408,12 +408,13 @@ public class StreamsPartitionAssignorTest {
configureDefault();
final ClientState state = new ClientState();
final List<TaskId> allTasks = asList(task0_0, task0_1, task0_2, task0_3, task1_0, task1_1, task1_2, task1_3);
final List<TaskId> allTasks = asList(TASK_0_0, TASK_0_1, TASK_0_2, TASK_0_3, TASK_1_0, TASK_1_1, TASK_1_2,
TASK_1_3);
final Map<String, List<TaskId>> previousAssignment = mkMap(
mkEntry(CONSUMER_1, new ArrayList<>(asList(task1_1, task1_3))),
mkEntry(CONSUMER_2, new ArrayList<>(asList(task0_3, task1_0))),
mkEntry(CONSUMER_3, new ArrayList<>(asList(task0_1, task0_2, task1_2)))
mkEntry(CONSUMER_1, new ArrayList<>(asList(TASK_1_1, TASK_1_3))),
mkEntry(CONSUMER_2, new ArrayList<>(asList(TASK_0_3, TASK_1_0))),
mkEntry(CONSUMER_3, new ArrayList<>(asList(TASK_0_1, TASK_0_2, TASK_1_2)))
);
for (final Map.Entry<String, List<TaskId>> entry : previousAssignment.entrySet()) {
@ -422,8 +423,8 @@ public class StreamsPartitionAssignorTest {
}
}
// Add the partitions of task0_0 to allOwnedPartitions but not c1's ownedPartitions/previousAssignment
final Set<TopicPartition> allOwnedPartitions = new HashSet<>(partitionsForTask.get(task0_0));
// Add the partitions of TASK_0_0 to allOwnedPartitions but not c1's ownedPartitions/previousAssignment
final Set<TopicPartition> allOwnedPartitions = new HashSet<>(partitionsForTask.get(TASK_0_0));
final Set<String> consumers = mkSet(CONSUMER_1, CONSUMER_2, CONSUMER_3);
state.assignActiveTasks(allTasks);
@ -493,7 +494,7 @@ public class StreamsPartitionAssignorTest {
Collections.sort(subscription.topics());
assertEquals(asList("topic1", "topic2"), subscription.topics());
final SubscriptionInfo info = getInfo(uuid1, prevTasks, standbyTasks);
final SubscriptionInfo info = getInfo(UUID_1, prevTasks, standbyTasks);
assertEquals(info, SubscriptionInfo.decode(subscription.userData()));
}
@ -519,7 +520,7 @@ public class StreamsPartitionAssignorTest {
Collections.sort(subscription.topics());
assertEquals(asList("topic1", "topic2"), subscription.topics());
final SubscriptionInfo info = getInfo(uuid1, prevTasks, standbyTasks);
final SubscriptionInfo info = getInfo(UUID_1, prevTasks, standbyTasks);
assertEquals(info, SubscriptionInfo.decode(subscription.userData()));
}
@ -530,14 +531,14 @@ public class StreamsPartitionAssignorTest {
builder.addProcessor("processor", new MockProcessorSupplier(), "source1", "source2");
builder.addStateStore(new MockKeyValueStoreBuilder("store", false), "processor");
final List<String> topics = asList("topic1", "topic2");
final Set<TaskId> allTasks = mkSet(task0_0, task0_1, task0_2);
final Set<TaskId> allTasks = mkSet(TASK_0_0, TASK_0_1, TASK_0_2);
final Set<TaskId> prevTasks10 = mkSet(task0_0);
final Set<TaskId> prevTasks11 = mkSet(task0_1);
final Set<TaskId> prevTasks20 = mkSet(task0_2);
final Set<TaskId> standbyTasks10 = mkSet(task0_1);
final Set<TaskId> standbyTasks11 = mkSet(task0_2);
final Set<TaskId> standbyTasks20 = mkSet(task0_0);
final Set<TaskId> prevTasks10 = mkSet(TASK_0_0);
final Set<TaskId> prevTasks11 = mkSet(TASK_0_1);
final Set<TaskId> prevTasks20 = mkSet(TASK_0_2);
final Set<TaskId> standbyTasks10 = mkSet(TASK_0_1);
final Set<TaskId> standbyTasks11 = mkSet(TASK_0_2);
final Set<TaskId> standbyTasks20 = mkSet(TASK_0_0);
createMockTaskManager(prevTasks10, standbyTasks10);
createMockAdminClient(getTopicPartitionOffsetsMap(
@ -549,17 +550,17 @@ public class StreamsPartitionAssignorTest {
subscriptions.put("consumer10",
new Subscription(
topics,
getInfo(uuid1, prevTasks10, standbyTasks10).encode()
getInfo(UUID_1, prevTasks10, standbyTasks10).encode()
));
subscriptions.put("consumer11",
new Subscription(
topics,
getInfo(uuid1, prevTasks11, standbyTasks11).encode()
getInfo(UUID_1, prevTasks11, standbyTasks11).encode()
));
subscriptions.put("consumer20",
new Subscription(
topics,
getInfo(uuid2, prevTasks20, standbyTasks20).encode()
getInfo(UUID_2, prevTasks20, standbyTasks20).encode()
));
final Map<String, Assignment> assignments = partitionAssignor.assign(metadata, new GroupSubscription(subscriptions)).groupAssignment();
@ -581,7 +582,7 @@ public class StreamsPartitionAssignorTest {
final AssignmentInfo info11 = checkAssignment(allTopics, assignments.get("consumer11"));
allActiveTasks.addAll(info11.activeTasks());
assertEquals(mkSet(task0_0, task0_1), allActiveTasks);
assertEquals(mkSet(TASK_0_0, TASK_0_1), allActiveTasks);
// the third consumer
final AssignmentInfo info20 = checkAssignment(allTopics, assignments.get("consumer20"));
@ -673,7 +674,7 @@ public class StreamsPartitionAssignorTest {
builder.addProcessor("processor2", new MockProcessorSupplier(), "source2");
builder.addStateStore(new MockKeyValueStoreBuilder("store2", false), "processor2");
final List<String> topics = asList("topic1", "topic2");
final Set<TaskId> allTasks = mkSet(task0_0, task0_1, task0_2);
final Set<TaskId> allTasks = mkSet(TASK_0_0, TASK_0_1, TASK_0_2);
createDefaultMockTaskManager();
createMockAdminClient(getTopicPartitionOffsetsMap(
@ -704,10 +705,10 @@ public class StreamsPartitionAssignorTest {
builder.addSource(null, "source2", null, null, null, "topic2");
builder.addProcessor("processor", new MockProcessorSupplier(), "source1", "source2");
final List<String> topics = asList("topic1", "topic2");
final Set<TaskId> allTasks = mkSet(task0_0, task0_1, task0_2);
final Set<TaskId> allTasks = mkSet(TASK_0_0, TASK_0_1, TASK_0_2);
final Set<TaskId> prevTasks10 = mkSet(task0_0);
final Set<TaskId> standbyTasks10 = mkSet(task0_1);
final Set<TaskId> prevTasks10 = mkSet(TASK_0_0);
final Set<TaskId> standbyTasks10 = mkSet(TASK_0_1);
final Cluster emptyMetadata = new Cluster("cluster", Collections.singletonList(Node.noNode()),
emptySet(),
emptySet(),
@ -719,7 +720,7 @@ public class StreamsPartitionAssignorTest {
subscriptions.put("consumer10",
new Subscription(
topics,
getInfo(uuid1, prevTasks10, standbyTasks10).encode()
getInfo(UUID_1, prevTasks10, standbyTasks10).encode()
));
// initially metadata is empty
@ -760,28 +761,28 @@ public class StreamsPartitionAssignorTest {
builder.addSource(null, "source3", null, null, null, "topic3");
builder.addProcessor("processor", new MockProcessorSupplier(), "source1", "source2", "source3");
final List<String> topics = asList("topic1", "topic2", "topic3");
final Set<TaskId> allTasks = mkSet(task0_0, task0_1, task0_2, task0_3);
final Set<TaskId> allTasks = mkSet(TASK_0_0, TASK_0_1, TASK_0_2, TASK_0_3);
// assuming that previous tasks do not have topic3
final Set<TaskId> prevTasks10 = mkSet(task0_0);
final Set<TaskId> prevTasks11 = mkSet(task0_1);
final Set<TaskId> prevTasks20 = mkSet(task0_2);
final Set<TaskId> prevTasks10 = mkSet(TASK_0_0);
final Set<TaskId> prevTasks11 = mkSet(TASK_0_1);
final Set<TaskId> prevTasks20 = mkSet(TASK_0_2);
createMockTaskManager(prevTasks10, emptyTasks);
createMockTaskManager(prevTasks10, EMPTY_TASKS);
configureDefaultPartitionAssignor();
subscriptions.put("consumer10",
new Subscription(
topics,
getInfo(uuid1, prevTasks10, emptyTasks).encode()));
getInfo(UUID_1, prevTasks10, EMPTY_TASKS).encode()));
subscriptions.put("consumer11",
new Subscription(
topics,
getInfo(uuid1, prevTasks11, emptyTasks).encode()));
getInfo(UUID_1, prevTasks11, EMPTY_TASKS).encode()));
subscriptions.put("consumer20",
new Subscription(
topics,
getInfo(uuid2, prevTasks20, emptyTasks).encode()));
getInfo(UUID_2, prevTasks20, EMPTY_TASKS).encode()));
final Map<String, Assignment> assignments = partitionAssignor.assign(metadata, new GroupSubscription(subscriptions)).groupAssignment();
@ -839,7 +840,7 @@ public class StreamsPartitionAssignorTest {
subscriptions.put("consumer11",
new Subscription(topics, defaultSubscriptionInfo.encode()));
subscriptions.put("consumer20",
new Subscription(topics, getInfo(uuid2, emptyTasks, emptyTasks).encode()));
new Subscription(topics, getInfo(UUID_2, EMPTY_TASKS, EMPTY_TASKS).encode()));
final Map<String, Assignment> assignments = partitionAssignor.assign(metadata, new GroupSubscription(subscriptions)).groupAssignment();
@ -897,17 +898,17 @@ public class StreamsPartitionAssignorTest {
final List<String> topics = asList("topic1", "topic2");
createMockTaskManager(mkSet(task0_0), emptySet());
createMockTaskManager(mkSet(TASK_0_0), emptySet());
configurePartitionAssignorWith(Collections.singletonMap(StreamsConfig.NUM_STANDBY_REPLICAS_CONFIG, 1));
subscriptions.put("consumer10",
new Subscription(
topics,
getInfo(uuid1, mkSet(task0_0), emptySet()).encode()));
getInfo(UUID_1, mkSet(TASK_0_0), emptySet()).encode()));
subscriptions.put("consumer20",
new Subscription(
topics,
getInfo(uuid2, mkSet(task0_2), emptySet()).encode()));
getInfo(UUID_2, mkSet(TASK_0_2), emptySet()).encode()));
final Map<String, Assignment> assignments =
partitionAssignor.assign(metadata, new GroupSubscription(subscriptions)).groupAssignment();
@ -927,17 +928,17 @@ public class StreamsPartitionAssignorTest {
final List<String> topics = asList("topic1", "topic2");
createMockTaskManager(mkSet(task0_0), emptySet());
createMockTaskManager(mkSet(TASK_0_0), emptySet());
configurePartitionAssignorWith(Collections.singletonMap(StreamsConfig.NUM_STANDBY_REPLICAS_CONFIG, 1));
subscriptions.put("consumer10",
new Subscription(
topics,
getInfo(uuid1, mkSet(task0_0), emptySet()).encode()));
getInfo(UUID_1, mkSet(TASK_0_0), emptySet()).encode()));
subscriptions.put("consumer20",
new Subscription(
topics,
getInfo(uuid2, mkSet(task0_2), emptySet()).encode()));
getInfo(UUID_2, mkSet(TASK_0_2), emptySet()).encode()));
final Map<String, Assignment> assignments =
partitionAssignor.assign(metadata, new GroupSubscription(subscriptions)).groupAssignment();
@ -962,14 +963,14 @@ public class StreamsPartitionAssignorTest {
.flatMap(Collection::stream)
.collect(Collectors.toSet());
final Set<TaskId> allTasks = mkSet(task0_0, task0_1, task0_2);
final Set<TaskId> allTasks = mkSet(TASK_0_0, TASK_0_1, TASK_0_2);
final Set<TaskId> prevTasks00 = mkSet(task0_0);
final Set<TaskId> prevTasks01 = mkSet(task0_1);
final Set<TaskId> prevTasks02 = mkSet(task0_2);
final Set<TaskId> standbyTasks00 = mkSet(task0_0);
final Set<TaskId> standbyTasks01 = mkSet(task0_1);
final Set<TaskId> standbyTasks02 = mkSet(task0_2);
final Set<TaskId> prevTasks00 = mkSet(TASK_0_0);
final Set<TaskId> prevTasks01 = mkSet(TASK_0_1);
final Set<TaskId> prevTasks02 = mkSet(TASK_0_2);
final Set<TaskId> standbyTasks00 = mkSet(TASK_0_0);
final Set<TaskId> standbyTasks01 = mkSet(TASK_0_1);
final Set<TaskId> standbyTasks02 = mkSet(TASK_0_2);
createMockTaskManager(prevTasks00, standbyTasks01);
createMockAdminClient(getTopicPartitionOffsetsMap(
@ -981,15 +982,15 @@ public class StreamsPartitionAssignorTest {
subscriptions.put("consumer10",
new Subscription(
topics,
getInfo(uuid1, prevTasks00, standbyTasks01, USER_END_POINT).encode()));
getInfo(UUID_1, prevTasks00, standbyTasks01, USER_END_POINT).encode()));
subscriptions.put("consumer11",
new Subscription(
topics,
getInfo(uuid1, prevTasks01, standbyTasks02, USER_END_POINT).encode()));
getInfo(UUID_1, prevTasks01, standbyTasks02, USER_END_POINT).encode()));
subscriptions.put("consumer20",
new Subscription(
topics,
getInfo(uuid2, prevTasks02, standbyTasks00, OTHER_END_POINT).encode()));
getInfo(UUID_2, prevTasks02, standbyTasks00, OTHER_END_POINT).encode()));
final Map<String, Assignment> assignments =
partitionAssignor.assign(metadata, new GroupSubscription(subscriptions)).groupAssignment();
@ -1007,8 +1008,8 @@ public class StreamsPartitionAssignorTest {
assertNotEquals("same processId has same set of standby tasks", info11.standbyTasks().keySet(), info10.standbyTasks().keySet());
// check active tasks assigned to the first client
assertEquals(mkSet(task0_0, task0_1), new HashSet<>(allActiveTasks));
assertEquals(mkSet(task0_2), new HashSet<>(allStandbyTasks));
assertEquals(mkSet(TASK_0_0, TASK_0_1), new HashSet<>(allActiveTasks));
assertEquals(mkSet(TASK_0_2), new HashSet<>(allStandbyTasks));
// the third consumer
final AssignmentInfo info20 = checkAssignment(allTopics, assignments.get("consumer20"));
@ -1053,11 +1054,11 @@ public class StreamsPartitionAssignorTest {
mkSet(t3p0, t3p3));
final Map<TaskId, Set<TopicPartition>> activeTasks = new HashMap<>();
activeTasks.put(task0_0, mkSet(t3p0));
activeTasks.put(task0_3, mkSet(t3p3));
activeTasks.put(TASK_0_0, mkSet(t3p0));
activeTasks.put(TASK_0_3, mkSet(t3p3));
final Map<TaskId, Set<TopicPartition>> standbyTasks = new HashMap<>();
standbyTasks.put(task0_1, mkSet(t3p1));
standbyTasks.put(task0_2, mkSet(t3p2));
standbyTasks.put(TASK_0_1, mkSet(t3p1));
standbyTasks.put(TASK_0_2, mkSet(t3p2));
taskManager.handleAssignment(activeTasks, standbyTasks);
EasyMock.expectLastCall();
@ -1069,7 +1070,7 @@ public class StreamsPartitionAssignorTest {
configureDefaultPartitionAssignor();
final List<TaskId> activeTaskList = asList(task0_0, task0_3);
final List<TaskId> activeTaskList = asList(TASK_0_0, TASK_0_3);
final AssignmentInfo info = new AssignmentInfo(LATEST_SUPPORTED_VERSION, activeTaskList, standbyTasks, hostState, emptyMap(), 0);
final Assignment assignment = new Assignment(asList(t3p0, t3p3), info.encode());
@ -1091,7 +1092,7 @@ public class StreamsPartitionAssignorTest {
builder.addSource(null, "source2", null, null, null, "topicX");
builder.addProcessor("processor2", new MockProcessorSupplier(), "source2");
final List<String> topics = asList("topic1", APPLICATION_ID + "-topicX");
final Set<TaskId> allTasks = mkSet(task0_0, task0_1, task0_2);
final Set<TaskId> allTasks = mkSet(TASK_0_0, TASK_0_1, TASK_0_2);
final MockInternalTopicManager internalTopicManager = configureDefault();
@ -1119,7 +1120,7 @@ public class StreamsPartitionAssignorTest {
builder.addSink("sink2", "topicZ", null, null, null, "processor2");
builder.addSource(null, "source3", null, null, null, "topicZ");
final List<String> topics = asList("topic1", APPLICATION_ID + "-topicX", APPLICATION_ID + "-topicZ");
final Set<TaskId> allTasks = mkSet(task0_0, task0_1, task0_2);
final Set<TaskId> allTasks = mkSet(TASK_0_0, TASK_0_1, TASK_0_2);
final MockInternalTopicManager internalTopicManager = configureDefault();
@ -1240,7 +1241,7 @@ public class StreamsPartitionAssignorTest {
subscriptions.put("consumer1",
new Subscription(
topics,
getInfo(uuid1, emptyTasks, emptyTasks, USER_END_POINT).encode())
getInfo(UUID_1, EMPTY_TASKS, EMPTY_TASKS, USER_END_POINT).encode())
);
final Map<String, Assignment> assignments = partitionAssignor.assign(metadata, new GroupSubscription(subscriptions)).groupAssignment();
final Assignment consumerAssignment = assignments.get("consumer1");
@ -1400,12 +1401,12 @@ public class StreamsPartitionAssignorTest {
subscriptions.put("consumer1",
new Subscription(
Collections.singletonList("topic1"),
getInfo(uuid1, emptyTasks, emptyTasks, USER_END_POINT).encode())
getInfo(UUID_1, EMPTY_TASKS, EMPTY_TASKS, USER_END_POINT).encode())
);
subscriptions.put("consumer2",
new Subscription(
Collections.singletonList("topic1"),
getInfo(uuid2, emptyTasks, emptyTasks, OTHER_END_POINT).encode())
getInfo(UUID_2, EMPTY_TASKS, EMPTY_TASKS, OTHER_END_POINT).encode())
);
final Set<TopicPartition> allPartitions = mkSet(t1p0, t1p1, t1p2);
final Map<String, Assignment> assign = partitionAssignor.assign(metadata, new GroupSubscription(subscriptions)).groupAssignment();
@ -1501,12 +1502,12 @@ public class StreamsPartitionAssignorTest {
subscriptions.put("consumer1",
new Subscription(
Collections.singletonList("topic1"),
getInfoForOlderVersion(smallestVersion, uuid1, emptyTasks, emptyTasks).encode())
getInfoForOlderVersion(smallestVersion, UUID_1, EMPTY_TASKS, EMPTY_TASKS).encode())
);
subscriptions.put("consumer2",
new Subscription(
Collections.singletonList("topic1"),
getInfoForOlderVersion(otherVersion, uuid2, emptyTasks, emptyTasks).encode()
getInfoForOlderVersion(otherVersion, UUID_2, EMPTY_TASKS, EMPTY_TASKS).encode()
)
);
@ -1569,18 +1570,18 @@ public class StreamsPartitionAssignorTest {
public void shouldReturnInterleavedAssignmentWithUnrevokedPartitionsRemovedWhenNewConsumerJoins() {
builder.addSource(null, "source1", null, null, null, "topic1");
final Set<TaskId> allTasks = mkSet(task0_0, task0_1, task0_2);
final Set<TaskId> allTasks = mkSet(TASK_0_0, TASK_0_1, TASK_0_2);
subscriptions.put(CONSUMER_1,
new Subscription(
Collections.singletonList("topic1"),
getInfo(uuid1, allTasks, emptyTasks).encode(),
getInfo(UUID_1, allTasks, EMPTY_TASKS).encode(),
asList(t1p0, t1p1, t1p2))
);
subscriptions.put(CONSUMER_2,
new Subscription(
Collections.singletonList("topic1"),
getInfo(uuid2, emptyTasks, emptyTasks).encode(),
getInfo(UUID_2, EMPTY_TASKS, EMPTY_TASKS).encode(),
emptyList())
);
@ -1611,16 +1612,16 @@ public class StreamsPartitionAssignorTest {
builder.addProcessor("processor", new MockProcessorSupplier(), "source1");
builder.addStateStore(new MockKeyValueStoreBuilder("store1", false), "processor");
final Set<TaskId> allTasks = mkSet(task0_0, task0_1, task0_2);
final Set<TaskId> allTasks = mkSet(TASK_0_0, TASK_0_1, TASK_0_2);
final Set<TaskId> activeTasks = mkSet(task0_0, task0_1);
final Set<TaskId> standbyTasks = mkSet(task0_2);
final Set<TaskId> activeTasks = mkSet(TASK_0_0, TASK_0_1);
final Set<TaskId> standbyTasks = mkSet(TASK_0_2);
final Map<TaskId, Set<TopicPartition>> standbyTaskMap = mkMap(
mkEntry(task0_2, Collections.singleton(t1p2))
mkEntry(TASK_0_2, Collections.singleton(t1p2))
);
final Map<TaskId, Set<TopicPartition>> futureStandbyTaskMap = mkMap(
mkEntry(task0_0, Collections.singleton(t1p0)),
mkEntry(task0_1, Collections.singleton(t1p1))
mkEntry(TASK_0_0, Collections.singleton(t1p0)),
mkEntry(TASK_0_1, Collections.singleton(t1p1))
);
createMockTaskManager(allTasks, allTasks);
@ -1634,7 +1635,7 @@ public class StreamsPartitionAssignorTest {
subscriptions.put("consumer1",
new Subscription(
Collections.singletonList("topic1"),
getInfo(uuid1, activeTasks, standbyTasks).encode(),
getInfo(UUID_1, activeTasks, standbyTasks).encode(),
asList(t1p0, t1p1))
);
subscriptions.put("future-consumer",
@ -1669,7 +1670,7 @@ public class StreamsPartitionAssignorTest {
equalTo(
new AssignmentInfo(
LATEST_SUPPORTED_VERSION,
Collections.singletonList(task0_2),
Collections.singletonList(TASK_0_2),
futureStandbyTaskMap,
emptyMap(),
emptyMap(),
@ -1682,7 +1683,7 @@ public class StreamsPartitionAssignorTest {
public void shouldReturnInterleavedAssignmentForOnlyFutureInstancesDuringVersionProbing() {
builder.addSource(null, "source1", null, null, null, "topic1");
final Set<TaskId> allTasks = mkSet(task0_0, task0_1, task0_2);
final Set<TaskId> allTasks = mkSet(TASK_0_0, TASK_0_1, TASK_0_2);
subscriptions.put(CONSUMER_1,
new Subscription(
@ -1708,13 +1709,13 @@ public class StreamsPartitionAssignorTest {
assertThat(assignment.get(CONSUMER_1).partitions(), equalTo(asList(t1p0, t1p2)));
assertThat(
AssignmentInfo.decode(assignment.get(CONSUMER_1).userData()),
equalTo(new AssignmentInfo(LATEST_SUPPORTED_VERSION, asList(task0_0, task0_2), emptyMap(), emptyMap(), emptyMap(), 0)));
equalTo(new AssignmentInfo(LATEST_SUPPORTED_VERSION, asList(TASK_0_0, TASK_0_2), emptyMap(), emptyMap(), emptyMap(), 0)));
assertThat(assignment.get(CONSUMER_2).partitions(), equalTo(Collections.singletonList(t1p1)));
assertThat(
AssignmentInfo.decode(assignment.get(CONSUMER_2).userData()),
equalTo(new AssignmentInfo(LATEST_SUPPORTED_VERSION, Collections.singletonList(task0_1), emptyMap(), emptyMap(), emptyMap(), 0)));
equalTo(new AssignmentInfo(LATEST_SUPPORTED_VERSION, Collections.singletonList(TASK_0_1), emptyMap(), emptyMap(), emptyMap(), 0)));
}
@Test
@ -1884,9 +1885,9 @@ public class StreamsPartitionAssignorTest {
builder.addSource(null, "source1", null, null, null, "topic1");
builder.addProcessor("processor1", new MockProcessorSupplier(), "source1");
builder.addStateStore(new MockKeyValueStoreBuilder("store1", false), "processor1");
final Set<TaskId> allTasks = mkSet(task0_0, task0_1, task0_2);
final Set<TaskId> allTasks = mkSet(TASK_0_0, TASK_0_1, TASK_0_2);
createMockTaskManager(allTasks, emptyTasks);
createMockTaskManager(allTasks, EMPTY_TASKS);
adminClient = EasyMock.createMock(AdminClient.class);
expect(adminClient.listOffsets(anyObject())).andThrow(new StreamsException("Should be handled"));
configureDefaultPartitionAssignor();
@ -1897,12 +1898,12 @@ public class StreamsPartitionAssignorTest {
subscriptions.put(firstConsumer,
new Subscription(
singletonList("source1"),
getInfo(uuid1, allTasks, emptyTasks).encode()
getInfo(UUID_1, allTasks, EMPTY_TASKS).encode()
));
subscriptions.put(newConsumer,
new Subscription(
singletonList("source1"),
getInfo(uuid2, emptyTasks, emptyTasks).encode()
getInfo(UUID_2, EMPTY_TASKS, EMPTY_TASKS).encode()
));
final Map<String, Assignment> assignments = partitionAssignor
@ -1930,7 +1931,7 @@ public class StreamsPartitionAssignorTest {
subscriptions.put("consumer1",
new Subscription(
Collections.singletonList("topic1"),
getInfoForOlderVersion(oldVersion, uuid1, emptyTasks, emptyTasks).encode())
getInfoForOlderVersion(oldVersion, UUID_1, EMPTY_TASKS, EMPTY_TASKS).encode())
);
subscriptions.put("future-consumer",
new Subscription(

View File

@ -30,20 +30,24 @@ import java.util.Set;
import static org.apache.kafka.common.utils.Utils.mkEntry;
import static org.apache.kafka.common.utils.Utils.mkMap;
import static org.apache.kafka.common.utils.Utils.mkSet;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_0_0;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_0_1;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_1_0;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_1_1;
import static org.apache.kafka.streams.processor.internals.assignment.StreamsAssignmentProtocolVersions.LATEST_SUPPORTED_VERSION;
import static org.apache.kafka.streams.processor.internals.assignment.StreamsAssignmentProtocolVersions.UNKNOWN;
import static org.junit.Assert.assertEquals;
public class AssignmentInfoTest {
private final List<TaskId> activeTasks = Arrays.asList(
new TaskId(0, 0),
new TaskId(0, 1),
new TaskId(1, 0),
new TaskId(1, 1));
TASK_0_0,
TASK_0_1,
TASK_1_0,
TASK_1_0);
private final Map<TaskId, Set<TopicPartition>> standbyTasks = mkMap(
mkEntry(new TaskId(1, 0), mkSet(new TopicPartition("t1", 0), new TopicPartition("t2", 0))),
mkEntry(new TaskId(1, 1), mkSet(new TopicPartition("t1", 1), new TopicPartition("t2", 1)))
mkEntry(TASK_1_0, mkSet(new TopicPartition("t1", 0), new TopicPartition("t2", 0))),
mkEntry(TASK_1_1, mkSet(new TopicPartition("t1", 1), new TopicPartition("t2", 1)))
);
private final Map<HostInfo, Set<TopicPartition>> activeAssignment = mkMap(

View File

@ -0,0 +1,86 @@
/*
* 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.kafka.streams.processor.internals.assignment;
import static java.util.Collections.emptyMap;
import static java.util.Collections.emptySet;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.streams.processor.TaskId;
public class AssignmentTestUtils {
public static final UUID UUID_1 = uuidForInt(1);
public static final UUID UUID_2 = uuidForInt(2);
public static final UUID UUID_3 = uuidForInt(3);
public static final UUID UUID_4 = uuidForInt(4);
public static final UUID UUID_5 = uuidForInt(5);
public static final UUID UUID_6 = uuidForInt(6);
public static final TaskId TASK_0_0 = new TaskId(0, 0);
public static final TaskId TASK_0_1 = new TaskId(0, 1);
public static final TaskId TASK_0_2 = new TaskId(0, 2);
public static final TaskId TASK_0_3 = new TaskId(0, 3);
public static final TaskId TASK_0_4 = new TaskId(0, 4);
public static final TaskId TASK_0_5 = new TaskId(0, 5);
public static final TaskId TASK_0_6 = new TaskId(0, 6);
public static final TaskId TASK_1_0 = new TaskId(1, 0);
public static final TaskId TASK_1_1 = new TaskId(1, 1);
public static final TaskId TASK_1_2 = new TaskId(1, 2);
public static final TaskId TASK_1_3 = new TaskId(1, 3);
public static final TaskId TASK_2_0 = new TaskId(2, 0);
public static final TaskId TASK_2_1 = new TaskId(2, 1);
public static final TaskId TASK_2_2 = new TaskId(2, 2);
public static final TaskId TASK_2_3 = new TaskId(2, 3);
public static final TaskId TASK_3_4 = new TaskId(3, 4);
public static final Set<TaskId> EMPTY_TASKS = emptySet();
public static final Map<TaskId, Long> EMPTY_TASK_OFFSET_SUMS = emptyMap();
public static final Map<TopicPartition, Long> EMPTY_CHANGELOG_END_OFFSETS = new HashMap<>();
/**
* Builds a UUID by repeating the given number n. For valid n, it is guaranteed that the returned UUIDs satisfy
* the same relation relative to others as their parameter n does: iff n < m, then uuidForInt(n) < uuidForInt(m)
*
* @param n an integer between 1 and 7
* @return the UUID created by repeating the digit n in the UUID format
*/
static UUID uuidForInt(final Integer n) {
if (n < 1 || n > 7) {
throw new IllegalArgumentException("Must pass in a single digit number to the uuid builder, got n = " + n);
}
final StringBuilder builder = new StringBuilder(36);
for (int i = 0; i < 8; ++i) {
builder.append(n);
}
builder.append('-');
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 4; ++j) {
builder.append(n);
}
builder.append('-');
}
for (int i = 0; i < 12; ++i) {
builder.append(n);
}
return UUID.fromString(builder.toString());
}
}

View File

@ -27,6 +27,8 @@ import java.util.Collections;
import static org.apache.kafka.common.utils.Utils.mkEntry;
import static org.apache.kafka.common.utils.Utils.mkMap;
import static org.apache.kafka.common.utils.Utils.mkSet;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_0_1;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_0_2;
import static org.apache.kafka.streams.processor.internals.assignment.SubscriptionInfo.UNKNOWN_OFFSET_SUM;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
@ -39,9 +41,6 @@ public class ClientStateTest {
private final ClientState client = new ClientState(1);
private final ClientState zeroCapacityClient = new ClientState(0);
private final TaskId taskId01 = new TaskId(0, 1);
private final TaskId taskId02 = new TaskId(0, 2);
@Test
public void shouldHaveNotReachedCapacityWhenAssignedTasksLessThanCapacity() {
assertFalse(client.reachedCapacity());
@ -49,64 +48,64 @@ public class ClientStateTest {
@Test
public void shouldHaveReachedCapacityWhenAssignedTasksGreaterThanOrEqualToCapacity() {
client.assignActive(taskId01);
client.assignActive(TASK_0_1);
assertTrue(client.reachedCapacity());
}
@Test
public void shouldAddActiveTasksToBothAssignedAndActive() {
client.assignActive(taskId01);
assertThat(client.activeTasks(), equalTo(Collections.singleton(taskId01)));
assertThat(client.assignedTasks(), equalTo(Collections.singleton(taskId01)));
client.assignActive(TASK_0_1);
assertThat(client.activeTasks(), equalTo(Collections.singleton(TASK_0_1)));
assertThat(client.assignedTasks(), equalTo(Collections.singleton(TASK_0_1)));
assertThat(client.assignedTaskCount(), equalTo(1));
assertThat(client.standbyTasks().size(), equalTo(0));
}
@Test
public void shouldAddStandbyTasksToBothStandbyAndAssigned() {
client.assignStandby(taskId01);
assertThat(client.assignedTasks(), equalTo(Collections.singleton(taskId01)));
assertThat(client.standbyTasks(), equalTo(Collections.singleton(taskId01)));
client.assignStandby(TASK_0_1);
assertThat(client.assignedTasks(), equalTo(Collections.singleton(TASK_0_1)));
assertThat(client.standbyTasks(), equalTo(Collections.singleton(TASK_0_1)));
assertThat(client.assignedTaskCount(), equalTo(1));
assertThat(client.activeTasks().size(), equalTo(0));
}
@Test
public void shouldAddPreviousActiveTasksToPreviousAssignedAndPreviousActive() {
client.addPreviousActiveTasks(Utils.mkSet(taskId01, taskId02));
assertThat(client.prevActiveTasks(), equalTo(Utils.mkSet(taskId01, taskId02)));
assertThat(client.previousAssignedTasks(), equalTo(Utils.mkSet(taskId01, taskId02)));
client.addPreviousActiveTasks(Utils.mkSet(TASK_0_1, TASK_0_2));
assertThat(client.prevActiveTasks(), equalTo(Utils.mkSet(TASK_0_1, TASK_0_2)));
assertThat(client.previousAssignedTasks(), equalTo(Utils.mkSet(TASK_0_1, TASK_0_2)));
}
@Test
public void shouldAddPreviousStandbyTasksToPreviousAssignedAndPreviousStandby() {
client.addPreviousStandbyTasks(Utils.mkSet(taskId01, taskId02));
client.addPreviousStandbyTasks(Utils.mkSet(TASK_0_1, TASK_0_2));
assertThat(client.prevActiveTasks().size(), equalTo(0));
assertThat(client.previousAssignedTasks(), equalTo(Utils.mkSet(taskId01, taskId02)));
assertThat(client.previousAssignedTasks(), equalTo(Utils.mkSet(TASK_0_1, TASK_0_2)));
}
@Test
public void shouldHaveAssignedTaskIfActiveTaskAssigned() {
client.assignActive(taskId01);
assertTrue(client.hasAssignedTask(taskId01));
client.assignActive(TASK_0_1);
assertTrue(client.hasAssignedTask(TASK_0_1));
}
@Test
public void shouldHaveAssignedTaskIfStandbyTaskAssigned() {
client.assignStandby(taskId01);
assertTrue(client.hasAssignedTask(taskId01));
client.assignStandby(TASK_0_1);
assertTrue(client.hasAssignedTask(TASK_0_1));
}
@Test
public void shouldNotHaveAssignedTaskIfTaskNotAssigned() {
client.assignActive(taskId01);
assertFalse(client.hasAssignedTask(taskId02));
client.assignActive(TASK_0_1);
assertFalse(client.hasAssignedTask(TASK_0_2));
}
@Test
public void shouldHaveMoreAvailableCapacityWhenCapacityTheSameButFewerAssignedTasks() {
final ClientState otherClient = new ClientState(1);
client.assignActive(taskId01);
client.assignActive(TASK_0_1);
assertTrue(otherClient.hasMoreAvailableCapacityThan(client));
assertFalse(client.hasMoreAvailableCapacityThan(otherClient));
}
@ -169,100 +168,100 @@ public class ClientStateTest {
@Test
public void shouldAddTasksWithLatestOffsetToPrevActiveTasks() {
final Map<TaskId, Long> taskOffsetSums = Collections.singletonMap(taskId01, Task.LATEST_OFFSET);
final Map<TaskId, Long> taskOffsetSums = Collections.singletonMap(TASK_0_1, Task.LATEST_OFFSET);
client.addPreviousTasksAndOffsetSums(taskOffsetSums);
client.initializePrevTasks(Collections.emptyMap());
assertThat(client.prevActiveTasks(), equalTo(Collections.singleton(taskId01)));
assertThat(client.previousAssignedTasks(), equalTo(Collections.singleton(taskId01)));
assertThat(client.prevActiveTasks(), equalTo(Collections.singleton(TASK_0_1)));
assertThat(client.previousAssignedTasks(), equalTo(Collections.singleton(TASK_0_1)));
assertTrue(client.prevStandbyTasks().isEmpty());
}
@Test
public void shouldAddTasksInOffsetSumsMapToPrevStandbyTasks() {
final Map<TaskId, Long> taskOffsetSums = mkMap(
mkEntry(taskId01, 0L),
mkEntry(taskId02, 100L)
mkEntry(TASK_0_1, 0L),
mkEntry(TASK_0_2, 100L)
);
client.addPreviousTasksAndOffsetSums(taskOffsetSums);
client.initializePrevTasks(Collections.emptyMap());
assertThat(client.prevStandbyTasks(), equalTo(mkSet(taskId01, taskId02)));
assertThat(client.previousAssignedTasks(), equalTo(mkSet(taskId01, taskId02)));
assertThat(client.prevStandbyTasks(), equalTo(mkSet(TASK_0_1, TASK_0_2)));
assertThat(client.previousAssignedTasks(), equalTo(mkSet(TASK_0_1, TASK_0_2)));
assertTrue(client.prevActiveTasks().isEmpty());
}
@Test
public void shouldComputeTaskLags() {
final Map<TaskId, Long> taskOffsetSums = mkMap(
mkEntry(taskId01, 0L),
mkEntry(taskId02, 100L)
mkEntry(TASK_0_1, 0L),
mkEntry(TASK_0_2, 100L)
);
final Map<TaskId, Long> allTaskEndOffsetSums = mkMap(
mkEntry(taskId01, 500L),
mkEntry(taskId02, 100L)
mkEntry(TASK_0_1, 500L),
mkEntry(TASK_0_2, 100L)
);
client.addPreviousTasksAndOffsetSums(taskOffsetSums);
client.computeTaskLags(null, allTaskEndOffsetSums);
assertThat(client.lagFor(taskId01), equalTo(500L));
assertThat(client.lagFor(taskId02), equalTo(0L));
assertThat(client.lagFor(TASK_0_1), equalTo(500L));
assertThat(client.lagFor(TASK_0_2), equalTo(0L));
}
@Test
public void shouldReturnEndOffsetSumForLagOfTaskWeDidNotPreviouslyOwn() {
final Map<TaskId, Long> taskOffsetSums = Collections.emptyMap();
final Map<TaskId, Long> allTaskEndOffsetSums = Collections.singletonMap(taskId01, 500L);
final Map<TaskId, Long> allTaskEndOffsetSums = Collections.singletonMap(TASK_0_1, 500L);
client.addPreviousTasksAndOffsetSums(taskOffsetSums);
client.computeTaskLags(null, allTaskEndOffsetSums);
assertThat(client.lagFor(taskId01), equalTo(500L));
assertThat(client.lagFor(TASK_0_1), equalTo(500L));
}
@Test
public void shouldReturnLatestOffsetForLagOfPreviousActiveRunningTask() {
final Map<TaskId, Long> taskOffsetSums = Collections.singletonMap(taskId01, Task.LATEST_OFFSET);
final Map<TaskId, Long> allTaskEndOffsetSums = Collections.singletonMap(taskId01, 500L);
final Map<TaskId, Long> taskOffsetSums = Collections.singletonMap(TASK_0_1, Task.LATEST_OFFSET);
final Map<TaskId, Long> allTaskEndOffsetSums = Collections.singletonMap(TASK_0_1, 500L);
client.addPreviousTasksAndOffsetSums(taskOffsetSums);
client.computeTaskLags(null, allTaskEndOffsetSums);
assertThat(client.lagFor(taskId01), equalTo(Task.LATEST_OFFSET));
assertThat(client.lagFor(TASK_0_1), equalTo(Task.LATEST_OFFSET));
}
@Test
public void shouldReturnUnknownOffsetSumForLagOfTaskWithUnknownOffset() {
final Map<TaskId, Long> taskOffsetSums = Collections.singletonMap(taskId01, UNKNOWN_OFFSET_SUM);
final Map<TaskId, Long> allTaskEndOffsetSums = Collections.singletonMap(taskId01, 500L);
final Map<TaskId, Long> taskOffsetSums = Collections.singletonMap(TASK_0_1, UNKNOWN_OFFSET_SUM);
final Map<TaskId, Long> allTaskEndOffsetSums = Collections.singletonMap(TASK_0_1, 500L);
client.addPreviousTasksAndOffsetSums(taskOffsetSums);
client.computeTaskLags(null, allTaskEndOffsetSums);
assertThat(client.lagFor(taskId01), equalTo(UNKNOWN_OFFSET_SUM));
assertThat(client.lagFor(TASK_0_1), equalTo(UNKNOWN_OFFSET_SUM));
}
@Test
public void shouldReturnEndOffsetSumIfOffsetSumIsGreaterThanEndOffsetSum() {
final Map<TaskId, Long> taskOffsetSums = Collections.singletonMap(taskId01, 5L);
final Map<TaskId, Long> allTaskEndOffsetSums = Collections.singletonMap(taskId01, 1L);
final Map<TaskId, Long> taskOffsetSums = Collections.singletonMap(TASK_0_1, 5L);
final Map<TaskId, Long> allTaskEndOffsetSums = Collections.singletonMap(TASK_0_1, 1L);
client.addPreviousTasksAndOffsetSums(taskOffsetSums);
client.computeTaskLags(null, allTaskEndOffsetSums);
assertThat(client.lagFor(taskId01), equalTo(1L));
assertThat(client.lagFor(TASK_0_1), equalTo(1L));
}
@Test
public void shouldThrowIllegalStateExceptionIfTaskLagsMapIsNotEmpty() {
final Map<TaskId, Long> taskOffsetSums = Collections.singletonMap(taskId01, 5L);
final Map<TaskId, Long> allTaskEndOffsetSums = Collections.singletonMap(taskId01, 1L);
final Map<TaskId, Long> taskOffsetSums = Collections.singletonMap(TASK_0_1, 5L);
final Map<TaskId, Long> allTaskEndOffsetSums = Collections.singletonMap(TASK_0_1, 1L);
client.computeTaskLags(null, taskOffsetSums);
assertThrows(IllegalStateException.class, () -> client.computeTaskLags(null, allTaskEndOffsetSums));
}
@Test
public void shouldThrowIllegalStateExceptionOnLagForUnknownTask() {
final Map<TaskId, Long> taskOffsetSums = Collections.singletonMap(taskId01, 0L);
final Map<TaskId, Long> allTaskEndOffsetSums = Collections.singletonMap(taskId01, 500L);
final Map<TaskId, Long> taskOffsetSums = Collections.singletonMap(TASK_0_1, 0L);
final Map<TaskId, Long> allTaskEndOffsetSums = Collections.singletonMap(TASK_0_1, 500L);
client.addPreviousTasksAndOffsetSums(taskOffsetSums);
client.computeTaskLags(null, allTaskEndOffsetSums);
assertThrows(IllegalStateException.class, () -> client.lagFor(taskId02));
assertThrows(IllegalStateException.class, () -> client.lagFor(TASK_0_2));
}
@Test
public void shouldThrowIllegalStateExceptionIfAttemptingToInitializeNonEmptyPrevTaskSets() {
client.addPreviousActiveTasks(Collections.singleton(taskId01));
client.addPreviousActiveTasks(Collections.singleton(TASK_0_1));
assertThrows(IllegalStateException.class, () -> client.initializePrevTasks(Collections.emptyMap()));
}

View File

@ -16,6 +16,7 @@
*/
package org.apache.kafka.streams.processor.internals.assignment;
import java.util.UUID;
import org.apache.kafka.streams.processor.TaskId;
import org.junit.Test;
@ -29,51 +30,49 @@ import java.util.TreeSet;
import static org.apache.kafka.common.utils.Utils.mkEntry;
import static org.apache.kafka.common.utils.Utils.mkMap;
import static org.apache.kafka.common.utils.Utils.mkSortedSet;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_0_0;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_0_1;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_0_2;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_1_0;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_1_1;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_1_2;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_2_0;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_2_1;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_2_2;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.UUID_1;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.UUID_2;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.UUID_3;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
public class DefaultBalancedAssignorTest {
private static final TaskId TASK_00 = new TaskId(0, 0);
private static final TaskId TASK_01 = new TaskId(0, 1);
private static final TaskId TASK_02 = new TaskId(0, 2);
private static final TaskId TASK_10 = new TaskId(1, 0);
private static final TaskId TASK_11 = new TaskId(1, 1);
private static final TaskId TASK_12 = new TaskId(1, 2);
private static final TaskId TASK_20 = new TaskId(2, 0);
private static final TaskId TASK_21 = new TaskId(2, 1);
private static final TaskId TASK_22 = new TaskId(2, 2);
private static final String CLIENT_1 = "client1";
private static final String CLIENT_2 = "client2";
private static final String CLIENT_3 = "client3";
private static final SortedSet<String> TWO_CLIENTS = new TreeSet<>(Arrays.asList(CLIENT_1, CLIENT_2));
private static final SortedSet<String> THREE_CLIENTS = new TreeSet<>(Arrays.asList(CLIENT_1, CLIENT_2, CLIENT_3));
private static final SortedSet<UUID> TWO_CLIENTS = new TreeSet<>(Arrays.asList(UUID_1, UUID_2));
private static final SortedSet<UUID> THREE_CLIENTS = new TreeSet<>(Arrays.asList(UUID_1, UUID_2, UUID_3));
@Test
public void shouldAssignTasksEvenlyOverClientsWhereNumberOfClientsIntegralDivisorOfNumberOfTasks() {
final int balanceFactor = 1;
final Map<String, List<TaskId>> assignment = new DefaultBalancedAssignor<String>().assign(
final Map<UUID, List<TaskId>> assignment = new DefaultBalancedAssignor().assign(
THREE_CLIENTS,
mkSortedSet(
TASK_00,
TASK_01,
TASK_02,
TASK_10,
TASK_11,
TASK_12,
TASK_20,
TASK_21,
TASK_22
TASK_0_0,
TASK_0_1,
TASK_0_2,
TASK_1_0,
TASK_1_1,
TASK_1_2,
TASK_2_0,
TASK_2_1,
TASK_2_2
),
threeClientsToNumberOfStreamThreads(1, 1, 1),
balanceFactor
);
final List<TaskId> assignedTasksForClient1 = Arrays.asList(TASK_00, TASK_10, TASK_20);
final List<TaskId> assignedTasksForClient2 = Arrays.asList(TASK_01, TASK_11, TASK_21);
final List<TaskId> assignedTasksForClient3 = Arrays.asList(TASK_02, TASK_12, TASK_22);
final List<TaskId> assignedTasksForClient1 = Arrays.asList(TASK_0_0, TASK_1_0, TASK_2_0);
final List<TaskId> assignedTasksForClient2 = Arrays.asList(TASK_0_1, TASK_1_1, TASK_2_1);
final List<TaskId> assignedTasksForClient3 = Arrays.asList(TASK_0_2, TASK_1_2, TASK_2_2);
assertThat(
assignment,
is(expectedAssignmentForThreeClients(assignedTasksForClient1, assignedTasksForClient2, assignedTasksForClient3))
@ -84,25 +83,25 @@ public class DefaultBalancedAssignorTest {
public void shouldAssignTasksEvenlyOverClientsWhereNumberOfClientsNotIntegralDivisorOfNumberOfTasks() {
final int balanceFactor = 1;
final Map<String, List<TaskId>> assignment = new DefaultBalancedAssignor<String>().assign(
final Map<UUID, List<TaskId>> assignment = new DefaultBalancedAssignor().assign(
TWO_CLIENTS,
mkSortedSet(
TASK_00,
TASK_01,
TASK_02,
TASK_10,
TASK_11,
TASK_12,
TASK_20,
TASK_21,
TASK_22
TASK_0_0,
TASK_0_1,
TASK_0_2,
TASK_1_0,
TASK_1_1,
TASK_1_2,
TASK_2_0,
TASK_2_1,
TASK_2_2
),
twoClientsToNumberOfStreamThreads(1, 1),
balanceFactor
);
final List<TaskId> assignedTasksForClient1 = Arrays.asList(TASK_00, TASK_02, TASK_11, TASK_20, TASK_22);
final List<TaskId> assignedTasksForClient2 = Arrays.asList(TASK_01, TASK_10, TASK_12, TASK_21);
final List<TaskId> assignedTasksForClient1 = Arrays.asList(TASK_0_0, TASK_0_2, TASK_1_1, TASK_2_0, TASK_2_2);
final List<TaskId> assignedTasksForClient2 = Arrays.asList(TASK_0_1, TASK_1_0, TASK_1_2, TASK_2_1);
assertThat(
assignment,
is(expectedAssignmentForTwoClients(assignedTasksForClient1, assignedTasksForClient2))
@ -113,26 +112,26 @@ public class DefaultBalancedAssignorTest {
public void shouldAssignTasksEvenlyOverClientsWhereNumberOfStreamThreadsIntegralDivisorOfNumberOfTasks() {
final int balanceFactor = 1;
final Map<String, List<TaskId>> assignment = new DefaultBalancedAssignor<String>().assign(
final Map<UUID, List<TaskId>> assignment = new DefaultBalancedAssignor().assign(
THREE_CLIENTS,
mkSortedSet(
TASK_00,
TASK_01,
TASK_02,
TASK_10,
TASK_11,
TASK_12,
TASK_20,
TASK_21,
TASK_22
TASK_0_0,
TASK_0_1,
TASK_0_2,
TASK_1_0,
TASK_1_1,
TASK_1_2,
TASK_2_0,
TASK_2_1,
TASK_2_2
),
threeClientsToNumberOfStreamThreads(3, 3, 3),
balanceFactor
);
final List<TaskId> assignedTasksForClient1 = Arrays.asList(TASK_00, TASK_10, TASK_20);
final List<TaskId> assignedTasksForClient2 = Arrays.asList(TASK_01, TASK_11, TASK_21);
final List<TaskId> assignedTasksForClient3 = Arrays.asList(TASK_02, TASK_12, TASK_22);
final List<TaskId> assignedTasksForClient1 = Arrays.asList(TASK_0_0, TASK_1_0, TASK_2_0);
final List<TaskId> assignedTasksForClient2 = Arrays.asList(TASK_0_1, TASK_1_1, TASK_2_1);
final List<TaskId> assignedTasksForClient3 = Arrays.asList(TASK_0_2, TASK_1_2, TASK_2_2);
assertThat(
assignment,
is(expectedAssignmentForThreeClients(assignedTasksForClient1, assignedTasksForClient2, assignedTasksForClient3))
@ -143,26 +142,26 @@ public class DefaultBalancedAssignorTest {
public void shouldAssignTasksEvenlyOverClientsWhereNumberOfStreamThreadsNotIntegralDivisorOfNumberOfTasks() {
final int balanceFactor = 1;
final Map<String, List<TaskId>> assignment = new DefaultBalancedAssignor<String>().assign(
final Map<UUID, List<TaskId>> assignment = new DefaultBalancedAssignor().assign(
THREE_CLIENTS,
mkSortedSet(
TASK_00,
TASK_01,
TASK_02,
TASK_10,
TASK_11,
TASK_12,
TASK_20,
TASK_21,
TASK_22
TASK_0_0,
TASK_0_1,
TASK_0_2,
TASK_1_0,
TASK_1_1,
TASK_1_2,
TASK_2_0,
TASK_2_1,
TASK_2_2
),
threeClientsToNumberOfStreamThreads(2, 2, 2),
balanceFactor
);
final List<TaskId> assignedTasksForClient1 = Arrays.asList(TASK_00, TASK_10, TASK_20);
final List<TaskId> assignedTasksForClient2 = Arrays.asList(TASK_01, TASK_11, TASK_21);
final List<TaskId> assignedTasksForClient3 = Arrays.asList(TASK_02, TASK_12, TASK_22);
final List<TaskId> assignedTasksForClient1 = Arrays.asList(TASK_0_0, TASK_1_0, TASK_2_0);
final List<TaskId> assignedTasksForClient2 = Arrays.asList(TASK_0_1, TASK_1_1, TASK_2_1);
final List<TaskId> assignedTasksForClient3 = Arrays.asList(TASK_0_2, TASK_1_2, TASK_2_2);
assertThat(
assignment,
is(expectedAssignmentForThreeClients(assignedTasksForClient1, assignedTasksForClient2, assignedTasksForClient3))
@ -173,26 +172,26 @@ public class DefaultBalancedAssignorTest {
public void shouldAssignTasksEvenlyOverUnevenlyDistributedStreamThreads() {
final int balanceFactor = 1;
final Map<String, List<TaskId>> assignment = new DefaultBalancedAssignor<String>().assign(
final Map<UUID, List<TaskId>> assignment = new DefaultBalancedAssignor().assign(
THREE_CLIENTS,
mkSortedSet(
TASK_00,
TASK_01,
TASK_02,
TASK_10,
TASK_11,
TASK_12,
TASK_20,
TASK_21,
TASK_22
TASK_0_0,
TASK_0_1,
TASK_0_2,
TASK_1_0,
TASK_1_1,
TASK_1_2,
TASK_2_0,
TASK_2_1,
TASK_2_2
),
threeClientsToNumberOfStreamThreads(1, 2, 3),
balanceFactor
);
final List<TaskId> assignedTasksForClient1 = Arrays.asList(TASK_10, TASK_20);
final List<TaskId> assignedTasksForClient2 = Arrays.asList(TASK_01, TASK_11, TASK_21, TASK_00);
final List<TaskId> assignedTasksForClient3 = Arrays.asList(TASK_02, TASK_12, TASK_22);
final List<TaskId> assignedTasksForClient1 = Arrays.asList(TASK_1_0, TASK_2_0);
final List<TaskId> assignedTasksForClient2 = Arrays.asList(TASK_0_1, TASK_1_1, TASK_2_1, TASK_0_0);
final List<TaskId> assignedTasksForClient3 = Arrays.asList(TASK_0_2, TASK_1_2, TASK_2_2);
assertThat(
assignment,
is(expectedAssignmentForThreeClients(assignedTasksForClient1, assignedTasksForClient2, assignedTasksForClient3))
@ -203,18 +202,18 @@ public class DefaultBalancedAssignorTest {
public void shouldAssignTasksEvenlyOverClientsWithLessClientsThanTasks() {
final int balanceFactor = 1;
final Map<String, List<TaskId>> assignment = new DefaultBalancedAssignor<String>().assign(
final Map<UUID, List<TaskId>> assignment = new DefaultBalancedAssignor().assign(
THREE_CLIENTS,
mkSortedSet(
TASK_00,
TASK_01
TASK_0_0,
TASK_0_1
),
threeClientsToNumberOfStreamThreads(1, 1, 1),
balanceFactor
);
final List<TaskId> assignedTasksForClient1 = Collections.singletonList(TASK_00);
final List<TaskId> assignedTasksForClient2 = Collections.singletonList(TASK_01);
final List<TaskId> assignedTasksForClient1 = Collections.singletonList(TASK_0_0);
final List<TaskId> assignedTasksForClient2 = Collections.singletonList(TASK_0_1);
final List<TaskId> assignedTasksForClient3 = Collections.emptyList();
assertThat(
assignment,
@ -226,26 +225,26 @@ public class DefaultBalancedAssignorTest {
public void shouldAssignTasksEvenlyOverClientsAndStreamThreadsWithMoreStreamThreadsThanTasks() {
final int balanceFactor = 1;
final Map<String, List<TaskId>> assignment = new DefaultBalancedAssignor<String>().assign(
final Map<UUID, List<TaskId>> assignment = new DefaultBalancedAssignor().assign(
THREE_CLIENTS,
mkSortedSet(
TASK_00,
TASK_01,
TASK_02,
TASK_10,
TASK_11,
TASK_12,
TASK_20,
TASK_21,
TASK_22
TASK_0_0,
TASK_0_1,
TASK_0_2,
TASK_1_0,
TASK_1_1,
TASK_1_2,
TASK_2_0,
TASK_2_1,
TASK_2_2
),
threeClientsToNumberOfStreamThreads(6, 6, 6),
balanceFactor
);
final List<TaskId> assignedTasksForClient1 = Arrays.asList(TASK_00, TASK_10, TASK_20);
final List<TaskId> assignedTasksForClient2 = Arrays.asList(TASK_01, TASK_11, TASK_21);
final List<TaskId> assignedTasksForClient3 = Arrays.asList(TASK_02, TASK_12, TASK_22);
final List<TaskId> assignedTasksForClient1 = Arrays.asList(TASK_0_0, TASK_1_0, TASK_2_0);
final List<TaskId> assignedTasksForClient2 = Arrays.asList(TASK_0_1, TASK_1_1, TASK_2_1);
final List<TaskId> assignedTasksForClient3 = Arrays.asList(TASK_0_2, TASK_1_2, TASK_2_2);
assertThat(
assignment,
is(expectedAssignmentForThreeClients(assignedTasksForClient1, assignedTasksForClient2, assignedTasksForClient3))
@ -256,25 +255,26 @@ public class DefaultBalancedAssignorTest {
public void shouldAssignTasksEvenlyOverStreamThreadsButBestEffortOverClients() {
final int balanceFactor = 1;
final Map<String, List<TaskId>> assignment = new DefaultBalancedAssignor<String>().assign(
final Map<UUID, List<TaskId>> assignment = new DefaultBalancedAssignor().assign(
TWO_CLIENTS,
mkSortedSet(
TASK_00,
TASK_01,
TASK_02,
TASK_10,
TASK_11,
TASK_12,
TASK_20,
TASK_21,
TASK_22
TASK_0_0,
TASK_0_1,
TASK_0_2,
TASK_1_0,
TASK_1_1,
TASK_1_2,
TASK_2_0,
TASK_2_1,
TASK_2_2
),
twoClientsToNumberOfStreamThreads(6, 2),
balanceFactor
);
final List<TaskId> assignedTasksForClient1 = Arrays.asList(TASK_00, TASK_02, TASK_11, TASK_20, TASK_22, TASK_01);
final List<TaskId> assignedTasksForClient2 = Arrays.asList(TASK_10, TASK_12, TASK_21);
final List<TaskId> assignedTasksForClient1 = Arrays.asList(TASK_0_0, TASK_0_2, TASK_1_1, TASK_2_0, TASK_2_2,
TASK_0_1);
final List<TaskId> assignedTasksForClient2 = Arrays.asList(TASK_1_0, TASK_1_2, TASK_2_1);
assertThat(
assignment,
is(expectedAssignmentForTwoClients(assignedTasksForClient1, assignedTasksForClient2))
@ -285,64 +285,64 @@ public class DefaultBalancedAssignorTest {
public void shouldAssignTasksEvenlyOverClientsButNotOverStreamThreadsBecauseBalanceFactorSatisfied() {
final int balanceFactor = 2;
final Map<String, List<TaskId>> assignment = new DefaultBalancedAssignor<String>().assign(
final Map<UUID, List<TaskId>> assignment = new DefaultBalancedAssignor().assign(
TWO_CLIENTS,
mkSortedSet(
TASK_00,
TASK_01,
TASK_02,
TASK_10,
TASK_11,
TASK_12,
TASK_20,
TASK_21,
TASK_22
TASK_0_0,
TASK_0_1,
TASK_0_2,
TASK_1_0,
TASK_1_1,
TASK_1_2,
TASK_2_0,
TASK_2_1,
TASK_2_2
),
twoClientsToNumberOfStreamThreads(6, 2),
balanceFactor
);
final List<TaskId> assignedTasksForClient1 = Arrays.asList(TASK_00, TASK_02, TASK_11, TASK_20, TASK_22);
final List<TaskId> assignedTasksForClient2 = Arrays.asList(TASK_01, TASK_10, TASK_12, TASK_21);
final List<TaskId> assignedTasksForClient1 = Arrays.asList(TASK_0_0, TASK_0_2, TASK_1_1, TASK_2_0, TASK_2_2);
final List<TaskId> assignedTasksForClient2 = Arrays.asList(TASK_0_1, TASK_1_0, TASK_1_2, TASK_2_1);
assertThat(
assignment,
is(expectedAssignmentForTwoClients(assignedTasksForClient1, assignedTasksForClient2))
);
}
private static Map<String, Integer> twoClientsToNumberOfStreamThreads(final int numberOfStreamThread1,
private static Map<UUID, Integer> twoClientsToNumberOfStreamThreads(final int numberOfStreamThread1,
final int numberOfStreamThread2) {
return mkMap(
mkEntry(CLIENT_1, numberOfStreamThread1),
mkEntry(CLIENT_2, numberOfStreamThread2)
mkEntry(UUID_1, numberOfStreamThread1),
mkEntry(UUID_2, numberOfStreamThread2)
);
}
private static Map<String, Integer> threeClientsToNumberOfStreamThreads(final int numberOfStreamThread1,
private static Map<UUID, Integer> threeClientsToNumberOfStreamThreads(final int numberOfStreamThread1,
final int numberOfStreamThread2,
final int numberOfStreamThread3) {
return mkMap(
mkEntry(CLIENT_1, numberOfStreamThread1),
mkEntry(CLIENT_2, numberOfStreamThread2),
mkEntry(CLIENT_3, numberOfStreamThread3)
mkEntry(UUID_1, numberOfStreamThread1),
mkEntry(UUID_2, numberOfStreamThread2),
mkEntry(UUID_3, numberOfStreamThread3)
);
}
private static Map<String, List<TaskId>> expectedAssignmentForThreeClients(final List<TaskId> assignedTasksForClient1,
private static Map<UUID, List<TaskId>> expectedAssignmentForThreeClients(final List<TaskId> assignedTasksForClient1,
final List<TaskId> assignedTasksForClient2,
final List<TaskId> assignedTasksForClient3) {
return mkMap(
mkEntry(CLIENT_1, assignedTasksForClient1),
mkEntry(CLIENT_2, assignedTasksForClient2),
mkEntry(CLIENT_3, assignedTasksForClient3)
mkEntry(UUID_1, assignedTasksForClient1),
mkEntry(UUID_2, assignedTasksForClient2),
mkEntry(UUID_3, assignedTasksForClient3)
);
}
private static Map<String, List<TaskId>> expectedAssignmentForTwoClients(final List<TaskId> assignedTasksForClient1,
private static Map<UUID, List<TaskId>> expectedAssignmentForTwoClients(final List<TaskId> assignedTasksForClient1,
final List<TaskId> assignedTasksForClient2) {
return mkMap(
mkEntry(CLIENT_1, assignedTasksForClient1),
mkEntry(CLIENT_2, assignedTasksForClient2)
mkEntry(UUID_1, assignedTasksForClient1),
mkEntry(UUID_2, assignedTasksForClient2)
);
}
}

View File

@ -16,10 +16,9 @@
*/
package org.apache.kafka.streams.processor.internals.assignment;
import java.util.UUID;
import org.apache.kafka.streams.processor.TaskId;
import org.apache.kafka.streams.processor.internals.Task;
import org.apache.kafka.streams.processor.internals.assignment.HighAvailabilityTaskAssignor.RankedClient;
import org.junit.Test;
import java.util.Arrays;
@ -35,22 +34,20 @@ import java.util.TreeSet;
import static org.apache.kafka.common.utils.Utils.mkEntry;
import static org.apache.kafka.common.utils.Utils.mkMap;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_0_1;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_1_2;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_2_3;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_3_4;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.UUID_1;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.UUID_2;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.UUID_3;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
public class DefaultStateConstrainedBalancedAssignorTest {
private static final TaskId TASK_01 = new TaskId(0, 1);
private static final TaskId TASK_12 = new TaskId(1, 2);
private static final TaskId TASK_23 = new TaskId(2, 3);
private static final TaskId TASK_34 = new TaskId(3, 4);
private static final String CLIENT_1 = "client1";
private static final String CLIENT_2 = "client2";
private static final String CLIENT_3 = "client3";
private static final Set<String> TWO_CLIENTS = new HashSet<>(Arrays.asList(CLIENT_1, CLIENT_2));
private static final Set<String> THREE_CLIENTS = new HashSet<>(Arrays.asList(CLIENT_1, CLIENT_2, CLIENT_3));
private static final Set<UUID> TWO_CLIENTS = new HashSet<>(Arrays.asList(UUID_1, UUID_2));
private static final Set<UUID> THREE_CLIENTS = new HashSet<>(Arrays.asList(UUID_1, UUID_2, UUID_3));
@Test
public void shouldAssignTaskToCaughtUpClient() {
@ -58,14 +55,14 @@ public class DefaultStateConstrainedBalancedAssignorTest {
final long rankOfClient2 = Long.MAX_VALUE;
final int balanceFactor = 1;
final Map<String, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor<String>().assign(
final Map<UUID, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor().assign(
oneStatefulTasksToTwoRankedClients(rankOfClient1, rankOfClient2),
balanceFactor,
TWO_CLIENTS,
twoClientsToNumberOfStreamThreads(1, 1)
);
final List<TaskId> assignedTasksForClient1 = Collections.singletonList(TASK_01);
final List<TaskId> assignedTasksForClient1 = Collections.singletonList(TASK_0_1);
final List<TaskId> assignedTasksForClient2 = Collections.emptyList();
assertThat(assignment, is(expectedAssignmentForTwoClients(assignedTasksForClient1, assignedTasksForClient2)));
}
@ -76,7 +73,7 @@ public class DefaultStateConstrainedBalancedAssignorTest {
final long rankOfClient2 = Task.LATEST_OFFSET;
final int balanceFactor = 1;
final Map<String, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor<String>().assign(
final Map<UUID, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor().assign(
oneStatefulTasksToTwoRankedClients(rankOfClient1, rankOfClient2),
balanceFactor,
TWO_CLIENTS,
@ -84,7 +81,7 @@ public class DefaultStateConstrainedBalancedAssignorTest {
);
final List<TaskId> assignedTasksForClient1 = Collections.emptyList();
final List<TaskId> assignedTasksForClient2 = Collections.singletonList(TASK_01);
final List<TaskId> assignedTasksForClient2 = Collections.singletonList(TASK_0_1);
assertThat(assignment, is(expectedAssignmentForTwoClients(assignedTasksForClient1, assignedTasksForClient2)));
}
@ -94,7 +91,7 @@ public class DefaultStateConstrainedBalancedAssignorTest {
final long rankOfClient2 = Task.LATEST_OFFSET;
final int balanceFactor = 1;
final Map<String, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor<String>().assign(
final Map<UUID, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor().assign(
oneStatefulTasksToTwoRankedClients(rankOfClient1, rankOfClient2),
balanceFactor,
TWO_CLIENTS,
@ -102,7 +99,7 @@ public class DefaultStateConstrainedBalancedAssignorTest {
);
final List<TaskId> assignedTasksForClient1 = Collections.emptyList();
final List<TaskId> assignedTasksForClient2 = Collections.singletonList(TASK_01);
final List<TaskId> assignedTasksForClient2 = Collections.singletonList(TASK_0_1);
assertThat(assignment, is(expectedAssignmentForTwoClients(assignedTasksForClient1, assignedTasksForClient2)));
}
@ -112,14 +109,14 @@ public class DefaultStateConstrainedBalancedAssignorTest {
final long rankOfClient2 = 0;
final int balanceFactor = 1;
final Map<String, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor<String>().assign(
final Map<UUID, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor().assign(
oneStatefulTasksToTwoRankedClients(rankOfClient1, rankOfClient2),
balanceFactor,
TWO_CLIENTS,
twoClientsToNumberOfStreamThreads(1, 1)
);
final List<TaskId> assignedTasksForClient1 = Collections.singletonList(TASK_01);
final List<TaskId> assignedTasksForClient1 = Collections.singletonList(TASK_0_1);
final List<TaskId> assignedTasksForClient2 = Collections.emptyList();
assertThat(assignment, is(expectedAssignmentForTwoClients(assignedTasksForClient1, assignedTasksForClient2)));
}
@ -130,14 +127,14 @@ public class DefaultStateConstrainedBalancedAssignorTest {
final long rankOfClient2 = 5;
final int balanceFactor = 1;
final Map<String, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor<String>().assign(
final Map<UUID, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor().assign(
oneStatefulTasksToTwoRankedClients(rankOfClient1, rankOfClient2),
balanceFactor,
TWO_CLIENTS,
twoClientsToNumberOfStreamThreads(1, 1)
);
final List<TaskId> assignedTasksForClient1 = Collections.singletonList(TASK_01);
final List<TaskId> assignedTasksForClient1 = Collections.singletonList(TASK_0_1);
final List<TaskId> assignedTasksForClient2 = Collections.emptyList();
assertThat(assignment, is(expectedAssignmentForTwoClients(assignedTasksForClient1, assignedTasksForClient2)));
}
@ -150,7 +147,7 @@ public class DefaultStateConstrainedBalancedAssignorTest {
final long rankForTask12OnClient2 = 0;
final int balanceFactor = 1;
final Map<String, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor<String>().assign(
final Map<UUID, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor().assign(
twoStatefulTasksToTwoRankedClients(
rankForTask01OnClient1,
rankForTask01OnClient2,
@ -162,8 +159,8 @@ public class DefaultStateConstrainedBalancedAssignorTest {
twoClientsToNumberOfStreamThreads(1, 1)
);
final List<TaskId> assignedTasksForClient1 = Collections.singletonList(TASK_01);
final List<TaskId> assignedTasksForClient2 = Collections.singletonList(TASK_12);
final List<TaskId> assignedTasksForClient1 = Collections.singletonList(TASK_0_1);
final List<TaskId> assignedTasksForClient2 = Collections.singletonList(TASK_1_2);
assertThat(assignment, is(expectedAssignmentForTwoClients(assignedTasksForClient1, assignedTasksForClient2)));
}
@ -175,7 +172,7 @@ public class DefaultStateConstrainedBalancedAssignorTest {
final long rankForTask12OnClient2 = 0;
final int balanceFactor = 2;
final Map<String, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor<String>().assign(
final Map<UUID, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor().assign(
twoStatefulTasksToTwoRankedClients(
rankForTask01OnClient1,
rankForTask01OnClient2,
@ -187,8 +184,8 @@ public class DefaultStateConstrainedBalancedAssignorTest {
twoClientsToNumberOfStreamThreads(1, 1)
);
final List<TaskId> assignedTasksForClient1 = Collections.singletonList(TASK_01);
final List<TaskId> assignedTasksForClient2 = Collections.singletonList(TASK_12);
final List<TaskId> assignedTasksForClient1 = Collections.singletonList(TASK_0_1);
final List<TaskId> assignedTasksForClient2 = Collections.singletonList(TASK_1_2);
assertThat(assignment, is(expectedAssignmentForTwoClients(assignedTasksForClient1, assignedTasksForClient2)));
}
@ -205,7 +202,7 @@ public class DefaultStateConstrainedBalancedAssignorTest {
final long rankForTask23OnClient3 = 0;
final int balanceFactor = 1;
final Map<String, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor<String>().assign(
final Map<UUID, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor().assign(
threeStatefulTasksToThreeRankedClients(
rankForTask01OnClient1,
rankForTask01OnClient2,
@ -222,9 +219,9 @@ public class DefaultStateConstrainedBalancedAssignorTest {
threeClientsToNumberOfStreamThreads(1, 1, 1)
);
final List<TaskId> assignedTasksForClient1 = Collections.singletonList(TASK_01);
final List<TaskId> assignedTasksForClient2 = Collections.singletonList(TASK_23);
final List<TaskId> assignedTasksForClient3 = Collections.singletonList(TASK_12);
final List<TaskId> assignedTasksForClient1 = Collections.singletonList(TASK_0_1);
final List<TaskId> assignedTasksForClient2 = Collections.singletonList(TASK_2_3);
final List<TaskId> assignedTasksForClient3 = Collections.singletonList(TASK_1_2);
assertThat(
assignment,
is(expectedAssignmentForThreeClients(assignedTasksForClient1, assignedTasksForClient2, assignedTasksForClient3))
@ -239,7 +236,7 @@ public class DefaultStateConstrainedBalancedAssignorTest {
final long rankForTask12OnClient2 = 100;
final int balanceFactor = 1;
final Map<String, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor<String>().assign(
final Map<UUID, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor().assign(
twoStatefulTasksToTwoRankedClients(
rankForTask01OnClient1,
rankForTask01OnClient2,
@ -251,8 +248,8 @@ public class DefaultStateConstrainedBalancedAssignorTest {
twoClientsToNumberOfStreamThreads(1, 1)
);
final List<TaskId> assignedTasksForClient1 = Collections.singletonList(TASK_12);
final List<TaskId> assignedTasksForClient2 = Collections.singletonList(TASK_01);
final List<TaskId> assignedTasksForClient1 = Collections.singletonList(TASK_1_2);
final List<TaskId> assignedTasksForClient2 = Collections.singletonList(TASK_0_1);
assertThat(assignment, is(expectedAssignmentForTwoClients(assignedTasksForClient1, assignedTasksForClient2)));
}
@ -264,7 +261,7 @@ public class DefaultStateConstrainedBalancedAssignorTest {
final long rankForTask12OnClient2 = Task.LATEST_OFFSET;
final int balanceFactor = 1;
final Map<String, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor<String>().assign(
final Map<UUID, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor().assign(
twoStatefulTasksToTwoRankedClients(
rankForTask01OnClient1,
rankForTask01OnClient2,
@ -276,8 +273,8 @@ public class DefaultStateConstrainedBalancedAssignorTest {
twoClientsToNumberOfStreamThreads(1, 1)
);
final List<TaskId> assignedTasksForClient1 = Collections.singletonList(TASK_12);
final List<TaskId> assignedTasksForClient2 = Collections.singletonList(TASK_01);
final List<TaskId> assignedTasksForClient1 = Collections.singletonList(TASK_1_2);
final List<TaskId> assignedTasksForClient2 = Collections.singletonList(TASK_0_1);
assertThat(assignment, is(expectedAssignmentForTwoClients(assignedTasksForClient1, assignedTasksForClient2)));
}
@ -289,7 +286,7 @@ public class DefaultStateConstrainedBalancedAssignorTest {
final long rankForTask12OnClient2 = 0;
final int balanceFactor = 2;
final Map<String, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor<String>().assign(
final Map<UUID, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor().assign(
twoStatefulTasksToTwoRankedClients(
rankForTask01OnClient1,
rankForTask01OnClient2,
@ -301,7 +298,7 @@ public class DefaultStateConstrainedBalancedAssignorTest {
twoClientsToNumberOfStreamThreads(1, 1)
);
final List<TaskId> assignedTasksForClient1 = Arrays.asList(TASK_01, TASK_12);
final List<TaskId> assignedTasksForClient1 = Arrays.asList(TASK_0_1, TASK_1_2);
final List<TaskId> assignedTasksForClient2 = Collections.emptyList();
assertThat(assignment, is(expectedAssignmentForTwoClients(assignedTasksForClient1, assignedTasksForClient2)));
}
@ -314,7 +311,7 @@ public class DefaultStateConstrainedBalancedAssignorTest {
final long rankForTask12OnClient2 = 10;
final int balanceFactor = 1;
final Map<String, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor<String>().assign(
final Map<UUID, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor().assign(
twoStatefulTasksToTwoRankedClients(
rankForTask01OnClient1,
rankForTask01OnClient2,
@ -326,8 +323,8 @@ public class DefaultStateConstrainedBalancedAssignorTest {
twoClientsToNumberOfStreamThreads(1, 1)
);
final List<TaskId> assignedTasksForClient1 = Collections.singletonList(TASK_01);
final List<TaskId> assignedTasksForClient2 = Collections.singletonList(TASK_12);
final List<TaskId> assignedTasksForClient1 = Collections.singletonList(TASK_0_1);
final List<TaskId> assignedTasksForClient2 = Collections.singletonList(TASK_1_2);
assertThat(assignment, is(expectedAssignmentForTwoClients(assignedTasksForClient1, assignedTasksForClient2)));
}
@ -339,7 +336,7 @@ public class DefaultStateConstrainedBalancedAssignorTest {
final long rankForTask12OnClient2 = 10;
final int balanceFactor = 1;
final Map<String, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor<String>().assign(
final Map<UUID, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor().assign(
twoStatefulTasksToTwoRankedClients(
rankForTask01OnClient1,
rankForTask01OnClient2,
@ -351,8 +348,8 @@ public class DefaultStateConstrainedBalancedAssignorTest {
twoClientsToNumberOfStreamThreads(1, 1)
);
final List<TaskId> assignedTasksForClient1 = Collections.singletonList(TASK_12);
final List<TaskId> assignedTasksForClient2 = Collections.singletonList(TASK_01);
final List<TaskId> assignedTasksForClient1 = Collections.singletonList(TASK_1_2);
final List<TaskId> assignedTasksForClient2 = Collections.singletonList(TASK_0_1);
assertThat(assignment, is(expectedAssignmentForTwoClients(assignedTasksForClient1, assignedTasksForClient2)));
}
@ -364,7 +361,7 @@ public class DefaultStateConstrainedBalancedAssignorTest {
final long rankForTask12OnClient2 = 20;
final int balanceFactor = 2;
final Map<String, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor<String>().assign(
final Map<UUID, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor().assign(
twoStatefulTasksToTwoRankedClients(
rankForTask01OnClient1,
rankForTask01OnClient2,
@ -376,7 +373,7 @@ public class DefaultStateConstrainedBalancedAssignorTest {
twoClientsToNumberOfStreamThreads(1, 1)
);
final List<TaskId> assignedTasksForClient1 = Arrays.asList(TASK_01, TASK_12);
final List<TaskId> assignedTasksForClient1 = Arrays.asList(TASK_0_1, TASK_1_2);
final List<TaskId> assignedTasksForClient2 = Collections.emptyList();
assertThat(assignment, is(expectedAssignmentForTwoClients(assignedTasksForClient1, assignedTasksForClient2)));
}
@ -389,7 +386,7 @@ public class DefaultStateConstrainedBalancedAssignorTest {
final long rankForTask12OnClient2 = 50;
final int balanceFactor = 1;
final Map<String, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor<String>().assign(
final Map<UUID, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor().assign(
twoStatefulTasksToTwoRankedClients(
rankForTask01OnClient1,
rankForTask01OnClient2,
@ -401,8 +398,8 @@ public class DefaultStateConstrainedBalancedAssignorTest {
twoClientsToNumberOfStreamThreads(1, 1)
);
final List<TaskId> assignedTasksForClient1 = Collections.singletonList(TASK_12);
final List<TaskId> assignedTasksForClient2 = Collections.singletonList(TASK_01);
final List<TaskId> assignedTasksForClient1 = Collections.singletonList(TASK_1_2);
final List<TaskId> assignedTasksForClient2 = Collections.singletonList(TASK_0_1);
assertThat(assignment, is(expectedAssignmentForTwoClients(assignedTasksForClient1, assignedTasksForClient2)));
}
@ -419,7 +416,7 @@ public class DefaultStateConstrainedBalancedAssignorTest {
final long rankForTask23OnClient3 = 100;
final int balanceFactor = 1;
final Map<String, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor<String>().assign(
final Map<UUID, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor().assign(
threeStatefulTasksToThreeRankedClients(
rankForTask01OnClient1,
rankForTask01OnClient2,
@ -436,9 +433,9 @@ public class DefaultStateConstrainedBalancedAssignorTest {
threeClientsToNumberOfStreamThreads(1, 1, 1)
);
final List<TaskId> assignedTasksForClient1 = Collections.singletonList(TASK_01);
final List<TaskId> assignedTasksForClient2 = Collections.singletonList(TASK_23);
final List<TaskId> assignedTasksForClient3 = Collections.singletonList(TASK_12);
final List<TaskId> assignedTasksForClient1 = Collections.singletonList(TASK_0_1);
final List<TaskId> assignedTasksForClient2 = Collections.singletonList(TASK_2_3);
final List<TaskId> assignedTasksForClient3 = Collections.singletonList(TASK_1_2);
assertThat(
assignment,
is(expectedAssignmentForThreeClients(assignedTasksForClient1, assignedTasksForClient2, assignedTasksForClient3))
@ -453,7 +450,7 @@ public class DefaultStateConstrainedBalancedAssignorTest {
final long rankForTask12OnClient2 = 10;
final int balanceFactor = 2;
final Map<String, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor<String>().assign(
final Map<UUID, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor().assign(
twoStatefulTasksToTwoRankedClients(
rankForTask01OnClient1,
rankForTask01OnClient2,
@ -466,7 +463,7 @@ public class DefaultStateConstrainedBalancedAssignorTest {
);
final List<TaskId> assignedTasksForClient1 = Collections.emptyList();
final List<TaskId> assignedTasksForClient2 = Arrays.asList(TASK_01, TASK_12);
final List<TaskId> assignedTasksForClient2 = Arrays.asList(TASK_0_1, TASK_1_2);
assertThat(assignment, is(expectedAssignmentForTwoClients(assignedTasksForClient1, assignedTasksForClient2)));
}
@ -478,7 +475,7 @@ public class DefaultStateConstrainedBalancedAssignorTest {
final long rankForTask12OnClient2 = 40;
final int balanceFactor = 2;
final Map<String, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor<String>().assign(
final Map<UUID, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor().assign(
twoStatefulTasksToTwoRankedClients(
rankForTask01OnClient1,
rankForTask01OnClient2,
@ -490,16 +487,16 @@ public class DefaultStateConstrainedBalancedAssignorTest {
twoClientsToNumberOfStreamThreads(1, 1)
);
final List<TaskId> assignedTasksForClient1 = Collections.singletonList(TASK_01);
final List<TaskId> assignedTasksForClient2 = Collections.singletonList(TASK_12);
final List<TaskId> assignedTasksForClient1 = Collections.singletonList(TASK_0_1);
final List<TaskId> assignedTasksForClient2 = Collections.singletonList(TASK_1_2);
assertThat(assignment, is(expectedAssignmentForTwoClients(assignedTasksForClient1, assignedTasksForClient2)));
}
/**
* This test shows that in an assigment of one client the assumption that the set of tasks which are caught-up on
* the given client is followed by the set of tasks that are not caught-up on the given client does NOT hold.
* In fact, in this test, at some point during the execution of the algorithm the assignment for CLIENT_2
* contains TASK_34 followed by TASK_23. TASK_23 is caught-up on CLIENT_2 whereas TASK_34 is not.
* In fact, in this test, at some point during the execution of the algorithm the assignment for UUID_2
* contains TASK_3_4 followed by TASK_2_3. TASK_2_3 is caught-up on UUID_2 whereas TASK_3_4 is not.
*/
@Test
public void shouldEvenlyDistributeTasksOrderOfCaughtUpAndNotCaughtUpTaskIsMixedUpInIntermediateResults() {
@ -517,7 +514,7 @@ public class DefaultStateConstrainedBalancedAssignorTest {
final long rankForTask34OnClient3 = 100;
final int balanceFactor = 1;
final Map<String, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor<String>().assign(
final Map<UUID, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor().assign(
fourStatefulTasksToThreeRankedClients(
rankForTask01OnClient1,
rankForTask01OnClient2,
@ -537,9 +534,9 @@ public class DefaultStateConstrainedBalancedAssignorTest {
threeClientsToNumberOfStreamThreads(1, 1, 1)
);
final List<TaskId> assignedTasksForClient1 = Arrays.asList(TASK_01, TASK_12);
final List<TaskId> assignedTasksForClient2 = Collections.singletonList(TASK_23);
final List<TaskId> assignedTasksForClient3 = Collections.singletonList(TASK_34);
final List<TaskId> assignedTasksForClient1 = Arrays.asList(TASK_0_1, TASK_1_2);
final List<TaskId> assignedTasksForClient2 = Collections.singletonList(TASK_2_3);
final List<TaskId> assignedTasksForClient3 = Collections.singletonList(TASK_3_4);
assertThat(
assignment,
is(expectedAssignmentForThreeClients(assignedTasksForClient1, assignedTasksForClient2, assignedTasksForClient3))
@ -562,7 +559,7 @@ public class DefaultStateConstrainedBalancedAssignorTest {
final long rankForTask34OnClient3 = 90;
final int balanceFactor = 1;
final Map<String, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor<String>().assign(
final Map<UUID, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor().assign(
fourStatefulTasksToThreeRankedClients(
rankForTask01OnClient1,
rankForTask01OnClient2,
@ -582,9 +579,9 @@ public class DefaultStateConstrainedBalancedAssignorTest {
threeClientsToNumberOfStreamThreads(1, 1, 1)
);
final List<TaskId> assignedTasksForClient1 = Collections.singletonList(TASK_34);
final List<TaskId> assignedTasksForClient1 = Collections.singletonList(TASK_3_4);
final List<TaskId> assignedTasksForClient2 = Collections.emptyList();
final List<TaskId> assignedTasksForClient3 = Arrays.asList(TASK_01, TASK_23, TASK_12);
final List<TaskId> assignedTasksForClient3 = Arrays.asList(TASK_0_1, TASK_2_3, TASK_1_2);
assertThat(
assignment,
is(expectedAssignmentForThreeClients(assignedTasksForClient1, assignedTasksForClient2, assignedTasksForClient3))
@ -601,7 +598,7 @@ public class DefaultStateConstrainedBalancedAssignorTest {
final long rankForTask23OnClient2 = 0;
final int balanceFactor = 1;
final Map<String, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor<String>().assign(
final Map<UUID, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor().assign(
threeStatefulTasksToTwoRankedClients(
rankForTask01OnClient1,
rankForTask01OnClient2,
@ -615,8 +612,8 @@ public class DefaultStateConstrainedBalancedAssignorTest {
twoClientsToNumberOfStreamThreads(1, 2)
);
final List<TaskId> assignedTasksForClient1 = Collections.singletonList(TASK_01);
final List<TaskId> assignedTasksForClient2 = Arrays.asList(TASK_12, TASK_23);
final List<TaskId> assignedTasksForClient1 = Collections.singletonList(TASK_0_1);
final List<TaskId> assignedTasksForClient2 = Arrays.asList(TASK_1_2, TASK_2_3);
assertThat(assignment, is(expectedAssignmentForTwoClients(assignedTasksForClient1, assignedTasksForClient2)));
}
@ -632,7 +629,7 @@ public class DefaultStateConstrainedBalancedAssignorTest {
final long rankForTask34OnClient2 = 0;
final int balanceFactor = 1;
final Map<String, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor<String>().assign(
final Map<UUID, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor().assign(
fourStatefulTasksToTwoRankedClients(
rankForTask01OnClient1,
rankForTask01OnClient2,
@ -648,8 +645,8 @@ public class DefaultStateConstrainedBalancedAssignorTest {
twoClientsToNumberOfStreamThreads(1, 2)
);
final List<TaskId> assignedTasksForClient1 = Arrays.asList(TASK_01, TASK_23);
final List<TaskId> assignedTasksForClient2 = Arrays.asList(TASK_12, TASK_34);
final List<TaskId> assignedTasksForClient1 = Arrays.asList(TASK_0_1, TASK_2_3);
final List<TaskId> assignedTasksForClient2 = Arrays.asList(TASK_1_2, TASK_3_4);
assertThat(assignment, is(expectedAssignmentForTwoClients(assignedTasksForClient1, assignedTasksForClient2)));
}
@ -661,7 +658,7 @@ public class DefaultStateConstrainedBalancedAssignorTest {
final long rankForTask12OnClient2 = 0;
final int balanceFactor = 1;
final Map<String, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor<String>().assign(
final Map<UUID, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor().assign(
twoStatefulTasksToTwoRankedClients(
rankForTask01OnClient1,
rankForTask01OnClient2,
@ -673,8 +670,8 @@ public class DefaultStateConstrainedBalancedAssignorTest {
twoClientsToNumberOfStreamThreads(2, 1)
);
final List<TaskId> assignedTasksForClient1 = Collections.singletonList(TASK_01);
final List<TaskId> assignedTasksForClient2 = Collections.singletonList(TASK_12);
final List<TaskId> assignedTasksForClient1 = Collections.singletonList(TASK_0_1);
final List<TaskId> assignedTasksForClient2 = Collections.singletonList(TASK_1_2);
assertThat(assignment, is(expectedAssignmentForTwoClients(assignedTasksForClient1, assignedTasksForClient2)));
}
@ -690,7 +687,7 @@ public class DefaultStateConstrainedBalancedAssignorTest {
final long rankForTask34OnClient2 = 0;
final int balanceFactor = 1;
final Map<String, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor<String>().assign(
final Map<UUID, List<TaskId>> assignment = new DefaultStateConstrainedBalancedAssignor().assign(
fourStatefulTasksToTwoRankedClients(
rankForTask01OnClient1,
rankForTask01OnClient2,
@ -706,82 +703,82 @@ public class DefaultStateConstrainedBalancedAssignorTest {
twoClientsToNumberOfStreamThreads(1, 4)
);
final List<TaskId> assignedTasksForClient1 = Collections.singletonList(TASK_01);
final List<TaskId> assignedTasksForClient2 = Arrays.asList(TASK_12, TASK_34, TASK_23);
final List<TaskId> assignedTasksForClient1 = Collections.singletonList(TASK_0_1);
final List<TaskId> assignedTasksForClient2 = Arrays.asList(TASK_1_2, TASK_3_4, TASK_2_3);
assertThat(assignment, is(expectedAssignmentForTwoClients(assignedTasksForClient1, assignedTasksForClient2)));
}
private static Map<String, Integer> twoClientsToNumberOfStreamThreads(final int numberOfStreamThread1,
private static Map<UUID, Integer> twoClientsToNumberOfStreamThreads(final int numberOfStreamThread1,
final int numberOfStreamThread2) {
return mkMap(
mkEntry(CLIENT_1, numberOfStreamThread1),
mkEntry(CLIENT_2, numberOfStreamThread2)
mkEntry(UUID_1, numberOfStreamThread1),
mkEntry(UUID_2, numberOfStreamThread2)
);
}
private static Map<String, Integer> threeClientsToNumberOfStreamThreads(final int numberOfStreamThread1,
private static Map<UUID, Integer> threeClientsToNumberOfStreamThreads(final int numberOfStreamThread1,
final int numberOfStreamThread2,
final int numberOfStreamThread3) {
return mkMap(
mkEntry(CLIENT_1, numberOfStreamThread1),
mkEntry(CLIENT_2, numberOfStreamThread2),
mkEntry(CLIENT_3, numberOfStreamThread3)
mkEntry(UUID_1, numberOfStreamThread1),
mkEntry(UUID_2, numberOfStreamThread2),
mkEntry(UUID_3, numberOfStreamThread3)
);
}
private static SortedMap<TaskId, SortedSet<RankedClient<String>>> oneStatefulTasksToTwoRankedClients(final long rankOfClient1,
private static SortedMap<TaskId, SortedSet<RankedClient>> oneStatefulTasksToTwoRankedClients(final long rankOfClient1,
final long rankOfClient2) {
final SortedSet<RankedClient<String>> rankedClients01 = new TreeSet<>();
rankedClients01.add(new RankedClient<>(CLIENT_1, rankOfClient1));
rankedClients01.add(new RankedClient<>(CLIENT_2, rankOfClient2));
final SortedSet<RankedClient> rankedClients01 = new TreeSet<>();
rankedClients01.add(new RankedClient(UUID_1, rankOfClient1));
rankedClients01.add(new RankedClient(UUID_2, rankOfClient2));
return new TreeMap<>(
mkMap(mkEntry(TASK_01, rankedClients01))
mkMap(mkEntry(TASK_0_1, rankedClients01))
);
}
private static SortedMap<TaskId, SortedSet<RankedClient<String>>> twoStatefulTasksToTwoRankedClients(final long rankForTask01OnClient1,
private static SortedMap<TaskId, SortedSet<RankedClient>> twoStatefulTasksToTwoRankedClients(final long rankForTask01OnClient1,
final long rankForTask01OnClient2,
final long rankForTask12OnClient1,
final long rankForTask12OnClient2) {
final SortedSet<RankedClient<String>> rankedClients01 = new TreeSet<>();
rankedClients01.add(new RankedClient<>(CLIENT_1, rankForTask01OnClient1));
rankedClients01.add(new RankedClient<>(CLIENT_2, rankForTask01OnClient2));
final SortedSet<RankedClient<String>> rankedClients12 = new TreeSet<>();
rankedClients12.add(new RankedClient<>(CLIENT_1, rankForTask12OnClient1));
rankedClients12.add(new RankedClient<>(CLIENT_2, rankForTask12OnClient2));
final SortedSet<RankedClient> rankedClients01 = new TreeSet<>();
rankedClients01.add(new RankedClient(UUID_1, rankForTask01OnClient1));
rankedClients01.add(new RankedClient(UUID_2, rankForTask01OnClient2));
final SortedSet<RankedClient> rankedClients12 = new TreeSet<>();
rankedClients12.add(new RankedClient(UUID_1, rankForTask12OnClient1));
rankedClients12.add(new RankedClient(UUID_2, rankForTask12OnClient2));
return new TreeMap<>(
mkMap(
mkEntry(TASK_01, rankedClients01),
mkEntry(TASK_12, rankedClients12)
mkEntry(TASK_0_1, rankedClients01),
mkEntry(TASK_1_2, rankedClients12)
)
);
}
private static SortedMap<TaskId, SortedSet<RankedClient<String>>> threeStatefulTasksToTwoRankedClients(final long rankForTask01OnClient1,
private static SortedMap<TaskId, SortedSet<RankedClient>> threeStatefulTasksToTwoRankedClients(final long rankForTask01OnClient1,
final long rankForTask01OnClient2,
final long rankForTask12OnClient1,
final long rankForTask12OnClient2,
final long rankForTask23OnClient1,
final long rankForTask23OnClient2) {
final SortedSet<RankedClient<String>> rankedClients01 = new TreeSet<>();
rankedClients01.add(new RankedClient<>(CLIENT_1, rankForTask01OnClient1));
rankedClients01.add(new RankedClient<>(CLIENT_2, rankForTask01OnClient2));
final SortedSet<RankedClient<String>> rankedClients12 = new TreeSet<>();
rankedClients12.add(new RankedClient<>(CLIENT_1, rankForTask12OnClient1));
rankedClients12.add(new RankedClient<>(CLIENT_2, rankForTask12OnClient2));
final SortedSet<RankedClient<String>> rankedClients23 = new TreeSet<>();
rankedClients23.add(new RankedClient<>(CLIENT_1, rankForTask23OnClient1));
rankedClients23.add(new RankedClient<>(CLIENT_2, rankForTask23OnClient2));
final SortedSet<RankedClient> rankedClients01 = new TreeSet<>();
rankedClients01.add(new RankedClient(UUID_1, rankForTask01OnClient1));
rankedClients01.add(new RankedClient(UUID_2, rankForTask01OnClient2));
final SortedSet<RankedClient> rankedClients12 = new TreeSet<>();
rankedClients12.add(new RankedClient(UUID_1, rankForTask12OnClient1));
rankedClients12.add(new RankedClient(UUID_2, rankForTask12OnClient2));
final SortedSet<RankedClient> rankedClients23 = new TreeSet<>();
rankedClients23.add(new RankedClient(UUID_1, rankForTask23OnClient1));
rankedClients23.add(new RankedClient(UUID_2, rankForTask23OnClient2));
return new TreeMap<>(
mkMap(
mkEntry(TASK_01, rankedClients01),
mkEntry(TASK_12, rankedClients12),
mkEntry(TASK_23, rankedClients23)
mkEntry(TASK_0_1, rankedClients01),
mkEntry(TASK_1_2, rankedClients12),
mkEntry(TASK_2_3, rankedClients23)
)
);
}
private static SortedMap<TaskId, SortedSet<RankedClient<String>>> threeStatefulTasksToThreeRankedClients(final long rankForTask01OnClient1,
private static SortedMap<TaskId, SortedSet<RankedClient>> threeStatefulTasksToThreeRankedClients(final long rankForTask01OnClient1,
final long rankForTask01OnClient2,
final long rankForTask01OnClient3,
final long rankForTask12OnClient1,
@ -790,28 +787,28 @@ public class DefaultStateConstrainedBalancedAssignorTest {
final long rankForTask23OnClient1,
final long rankForTask23OnClient2,
final long rankForTask23OnClient3) {
final SortedSet<RankedClient<String>> rankedClients01 = new TreeSet<>();
rankedClients01.add(new RankedClient<>(CLIENT_1, rankForTask01OnClient1));
rankedClients01.add(new RankedClient<>(CLIENT_2, rankForTask01OnClient2));
rankedClients01.add(new RankedClient<>(CLIENT_3, rankForTask01OnClient3));
final SortedSet<RankedClient<String>> rankedClients12 = new TreeSet<>();
rankedClients12.add(new RankedClient<>(CLIENT_1, rankForTask12OnClient1));
rankedClients12.add(new RankedClient<>(CLIENT_2, rankForTask12OnClient2));
rankedClients12.add(new RankedClient<>(CLIENT_3, rankForTask12OnClient3));
final SortedSet<RankedClient<String>> rankedClients23 = new TreeSet<>();
rankedClients23.add(new RankedClient<>(CLIENT_1, rankForTask23OnClient1));
rankedClients23.add(new RankedClient<>(CLIENT_2, rankForTask23OnClient2));
rankedClients23.add(new RankedClient<>(CLIENT_3, rankForTask23OnClient3));
final SortedSet<RankedClient> rankedClients01 = new TreeSet<>();
rankedClients01.add(new RankedClient(UUID_1, rankForTask01OnClient1));
rankedClients01.add(new RankedClient(UUID_2, rankForTask01OnClient2));
rankedClients01.add(new RankedClient(UUID_3, rankForTask01OnClient3));
final SortedSet<RankedClient> rankedClients12 = new TreeSet<>();
rankedClients12.add(new RankedClient(UUID_1, rankForTask12OnClient1));
rankedClients12.add(new RankedClient(UUID_2, rankForTask12OnClient2));
rankedClients12.add(new RankedClient(UUID_3, rankForTask12OnClient3));
final SortedSet<RankedClient> rankedClients23 = new TreeSet<>();
rankedClients23.add(new RankedClient(UUID_1, rankForTask23OnClient1));
rankedClients23.add(new RankedClient(UUID_2, rankForTask23OnClient2));
rankedClients23.add(new RankedClient(UUID_3, rankForTask23OnClient3));
return new TreeMap<>(
mkMap(
mkEntry(TASK_01, rankedClients01),
mkEntry(TASK_12, rankedClients12),
mkEntry(TASK_23, rankedClients23)
mkEntry(TASK_0_1, rankedClients01),
mkEntry(TASK_1_2, rankedClients12),
mkEntry(TASK_2_3, rankedClients23)
)
);
}
private static SortedMap<TaskId, SortedSet<RankedClient<String>>> fourStatefulTasksToTwoRankedClients(final long rankForTask01OnClient1,
private static SortedMap<TaskId, SortedSet<RankedClient>> fourStatefulTasksToTwoRankedClients(final long rankForTask01OnClient1,
final long rankForTask01OnClient2,
final long rankForTask12OnClient1,
final long rankForTask12OnClient2,
@ -819,29 +816,29 @@ public class DefaultStateConstrainedBalancedAssignorTest {
final long rankForTask23OnClient2,
final long rankForTask34OnClient1,
final long rankForTask34OnClient2) {
final SortedSet<RankedClient<String>> rankedClients01 = new TreeSet<>();
rankedClients01.add(new RankedClient<>(CLIENT_1, rankForTask01OnClient1));
rankedClients01.add(new RankedClient<>(CLIENT_2, rankForTask01OnClient2));
final SortedSet<RankedClient<String>> rankedClients12 = new TreeSet<>();
rankedClients12.add(new RankedClient<>(CLIENT_1, rankForTask12OnClient1));
rankedClients12.add(new RankedClient<>(CLIENT_2, rankForTask12OnClient2));
final SortedSet<RankedClient<String>> rankedClients23 = new TreeSet<>();
rankedClients23.add(new RankedClient<>(CLIENT_1, rankForTask23OnClient1));
rankedClients23.add(new RankedClient<>(CLIENT_2, rankForTask23OnClient2));
final SortedSet<RankedClient<String>> rankedClients34 = new TreeSet<>();
rankedClients34.add(new RankedClient<>(CLIENT_1, rankForTask34OnClient1));
rankedClients34.add(new RankedClient<>(CLIENT_2, rankForTask34OnClient2));
final SortedSet<RankedClient> rankedClients01 = new TreeSet<>();
rankedClients01.add(new RankedClient(UUID_1, rankForTask01OnClient1));
rankedClients01.add(new RankedClient(UUID_2, rankForTask01OnClient2));
final SortedSet<RankedClient> rankedClients12 = new TreeSet<>();
rankedClients12.add(new RankedClient(UUID_1, rankForTask12OnClient1));
rankedClients12.add(new RankedClient(UUID_2, rankForTask12OnClient2));
final SortedSet<RankedClient> rankedClients23 = new TreeSet<>();
rankedClients23.add(new RankedClient(UUID_1, rankForTask23OnClient1));
rankedClients23.add(new RankedClient(UUID_2, rankForTask23OnClient2));
final SortedSet<RankedClient> rankedClients34 = new TreeSet<>();
rankedClients34.add(new RankedClient(UUID_1, rankForTask34OnClient1));
rankedClients34.add(new RankedClient(UUID_2, rankForTask34OnClient2));
return new TreeMap<>(
mkMap(
mkEntry(TASK_01, rankedClients01),
mkEntry(TASK_12, rankedClients12),
mkEntry(TASK_23, rankedClients23),
mkEntry(TASK_34, rankedClients34)
mkEntry(TASK_0_1, rankedClients01),
mkEntry(TASK_1_2, rankedClients12),
mkEntry(TASK_2_3, rankedClients23),
mkEntry(TASK_3_4, rankedClients34)
)
);
}
private static SortedMap<TaskId, SortedSet<RankedClient<String>>> fourStatefulTasksToThreeRankedClients(final long rankForTask01OnClient1,
private static SortedMap<TaskId, SortedSet<RankedClient>> fourStatefulTasksToThreeRankedClients(final long rankForTask01OnClient1,
final long rankForTask01OnClient2,
final long rankForTask01OnClient3,
final long rankForTask12OnClient1,
@ -853,47 +850,47 @@ public class DefaultStateConstrainedBalancedAssignorTest {
final long rankForTask34OnClient1,
final long rankForTask34OnClient2,
final long rankForTask34OnClient3) {
final SortedSet<RankedClient<String>> rankedClients01 = new TreeSet<>();
rankedClients01.add(new RankedClient<>(CLIENT_1, rankForTask01OnClient1));
rankedClients01.add(new RankedClient<>(CLIENT_2, rankForTask01OnClient2));
rankedClients01.add(new RankedClient<>(CLIENT_3, rankForTask01OnClient3));
final SortedSet<RankedClient<String>> rankedClients12 = new TreeSet<>();
rankedClients12.add(new RankedClient<>(CLIENT_1, rankForTask12OnClient1));
rankedClients12.add(new RankedClient<>(CLIENT_2, rankForTask12OnClient2));
rankedClients12.add(new RankedClient<>(CLIENT_3, rankForTask12OnClient3));
final SortedSet<RankedClient<String>> rankedClients23 = new TreeSet<>();
rankedClients23.add(new RankedClient<>(CLIENT_1, rankForTask23OnClient1));
rankedClients23.add(new RankedClient<>(CLIENT_2, rankForTask23OnClient2));
rankedClients23.add(new RankedClient<>(CLIENT_3, rankForTask23OnClient3));
final SortedSet<RankedClient<String>> rankedClients34 = new TreeSet<>();
rankedClients34.add(new RankedClient<>(CLIENT_1, rankForTask34OnClient1));
rankedClients34.add(new RankedClient<>(CLIENT_2, rankForTask34OnClient2));
rankedClients34.add(new RankedClient<>(CLIENT_3, rankForTask34OnClient3));
final SortedSet<RankedClient> rankedClients01 = new TreeSet<>();
rankedClients01.add(new RankedClient(UUID_1, rankForTask01OnClient1));
rankedClients01.add(new RankedClient(UUID_2, rankForTask01OnClient2));
rankedClients01.add(new RankedClient(UUID_3, rankForTask01OnClient3));
final SortedSet<RankedClient> rankedClients12 = new TreeSet<>();
rankedClients12.add(new RankedClient(UUID_1, rankForTask12OnClient1));
rankedClients12.add(new RankedClient(UUID_2, rankForTask12OnClient2));
rankedClients12.add(new RankedClient(UUID_3, rankForTask12OnClient3));
final SortedSet<RankedClient> rankedClients23 = new TreeSet<>();
rankedClients23.add(new RankedClient(UUID_1, rankForTask23OnClient1));
rankedClients23.add(new RankedClient(UUID_2, rankForTask23OnClient2));
rankedClients23.add(new RankedClient(UUID_3, rankForTask23OnClient3));
final SortedSet<RankedClient> rankedClients34 = new TreeSet<>();
rankedClients34.add(new RankedClient(UUID_1, rankForTask34OnClient1));
rankedClients34.add(new RankedClient(UUID_2, rankForTask34OnClient2));
rankedClients34.add(new RankedClient(UUID_3, rankForTask34OnClient3));
return new TreeMap<>(
mkMap(
mkEntry(TASK_01, rankedClients01),
mkEntry(TASK_12, rankedClients12),
mkEntry(TASK_23, rankedClients23),
mkEntry(TASK_34, rankedClients34)
mkEntry(TASK_0_1, rankedClients01),
mkEntry(TASK_1_2, rankedClients12),
mkEntry(TASK_2_3, rankedClients23),
mkEntry(TASK_3_4, rankedClients34)
)
);
}
private static Map<String, List<TaskId>> expectedAssignmentForTwoClients(final List<TaskId> assignedTasksForClient1,
private static Map<UUID, List<TaskId>> expectedAssignmentForTwoClients(final List<TaskId> assignedTasksForClient1,
final List<TaskId> assignedTasksForClient2) {
return mkMap(
mkEntry(CLIENT_1, assignedTasksForClient1),
mkEntry(CLIENT_2, assignedTasksForClient2)
mkEntry(UUID_1, assignedTasksForClient1),
mkEntry(UUID_2, assignedTasksForClient2)
);
}
private static Map<String, List<TaskId>> expectedAssignmentForThreeClients(final List<TaskId> assignedTasksForClient1,
private static Map<UUID, List<TaskId>> expectedAssignmentForThreeClients(final List<TaskId> assignedTasksForClient1,
final List<TaskId> assignedTasksForClient2,
final List<TaskId> assignedTasksForClient3) {
return mkMap(
mkEntry(CLIENT_1, assignedTasksForClient1),
mkEntry(CLIENT_2, assignedTasksForClient2),
mkEntry(CLIENT_3, assignedTasksForClient3)
mkEntry(UUID_1, assignedTasksForClient1),
mkEntry(UUID_2, assignedTasksForClient2),
mkEntry(UUID_3, assignedTasksForClient3)
);
}
}

View File

@ -16,40 +16,41 @@
*/
package org.apache.kafka.streams.processor.internals.assignment;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptySet;
import static java.util.Collections.singleton;
import static java.util.Collections.singletonMap;
import static org.apache.kafka.common.utils.Utils.mkEntry;
import static org.apache.kafka.common.utils.Utils.mkMap;
import static org.apache.kafka.common.utils.Utils.mkSet;
import static org.apache.kafka.common.utils.Utils.mkSortedSet;
import static org.apache.kafka.streams.processor.internals.assignment.HighAvailabilityTaskAssignor.buildClientRankingsByTask;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.EMPTY_TASKS;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_0_0;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_0_1;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_0_2;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_0_3;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_1_0;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_1_1;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_1_2;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_1_3;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_2_0;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_2_1;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_2_3;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.UUID_1;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.UUID_2;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.UUID_3;
import static org.apache.kafka.streams.processor.internals.assignment.HighAvailabilityTaskAssignor.computeBalanceFactor;
import static org.apache.kafka.streams.processor.internals.assignment.HighAvailabilityTaskAssignor.getMovements;
import static org.apache.kafka.streams.processor.internals.assignment.SubscriptionInfo.UNKNOWN_OFFSET_SUM;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.SortedSet;
import java.util.UUID;
import org.apache.kafka.streams.processor.TaskId;
import org.apache.kafka.streams.processor.internals.Task;
import org.apache.kafka.streams.processor.internals.assignment.AssignorConfiguration.AssignmentConfigs;
import org.apache.kafka.streams.processor.internals.assignment.HighAvailabilityTaskAssignor.Movement;
import org.apache.kafka.streams.processor.internals.assignment.HighAvailabilityTaskAssignor.RankedClient;
import org.easymock.EasyMock;
import org.junit.Test;
@ -60,33 +61,15 @@ public class HighAvailabilityTaskAssignorTest {
private int numStandbyReplicas = 0;
private long probingRebalanceInterval = 60 * 1000L;
private Map<String, ClientState> clientStates = new HashMap<>();
private Map<UUID, ClientState> clientStates = new HashMap<>();
private Set<TaskId> allTasks = new HashSet<>();
private Set<TaskId> statefulTasks = new HashSet<>();
private static final TaskId TASK_0_0 = new TaskId(0, 0);
private static final TaskId TASK_0_1 = new TaskId(0, 1);
private static final TaskId TASK_0_2 = new TaskId(0, 2);
private static final TaskId TASK_0_3 = new TaskId(0, 3);
private static final TaskId TASK_1_0 = new TaskId(1, 0);
private static final TaskId TASK_1_1 = new TaskId(1, 1);
private static final TaskId TASK_1_2 = new TaskId(1, 2);
private static final TaskId TASK_1_3 = new TaskId(1, 3);
private static final TaskId TASK_2_0 = new TaskId(2, 0);
private static final TaskId TASK_2_1 = new TaskId(2, 1);
private static final TaskId TASK_2_3 = new TaskId(2, 3);
private static final String ID_1 = "client1";
private static final String ID_2 = "client2";
private static final String ID_3 = "client3";
private ClientState client1;
private ClientState client2;
private ClientState client3;
private static final Set<TaskId> EMPTY_TASKS = emptySet();
private HighAvailabilityTaskAssignor<String> taskAssignor;
private HighAvailabilityTaskAssignor taskAssignor;
private void createTaskAssignor() {
final AssignmentConfigs configs = new AssignmentConfigs(
@ -96,216 +79,13 @@ public class HighAvailabilityTaskAssignorTest {
numStandbyReplicas,
probingRebalanceInterval
);
taskAssignor = new HighAvailabilityTaskAssignor<>(
taskAssignor = new HighAvailabilityTaskAssignor(
clientStates,
allTasks,
statefulTasks,
configs);
}
@Test
public void shouldRankPreviousClientAboveEquallyCaughtUpClient() {
client1 = EasyMock.createNiceMock(ClientState.class);
client2 = EasyMock.createNiceMock(ClientState.class);
expect(client1.lagFor(TASK_0_0)).andReturn(Task.LATEST_OFFSET);
expect(client2.lagFor(TASK_0_0)).andReturn(0L);
replay(client1, client2);
final SortedSet<RankedClient<String>> expectedClientRanking = mkSortedSet(
new RankedClient<>(ID_1, Task.LATEST_OFFSET),
new RankedClient<>(ID_2, 0L)
);
final Map<String, ClientState> states = mkMap(
mkEntry(ID_1, client1),
mkEntry(ID_2, client2)
);
final Map<TaskId, SortedSet<RankedClient<String>>> statefulTasksToRankedCandidates =
buildClientRankingsByTask(singleton(TASK_0_0), states, acceptableRecoveryLag);
final SortedSet<RankedClient<String>> clientRanking = statefulTasksToRankedCandidates.get(TASK_0_0);
EasyMock.verify(client1, client2);
assertThat(clientRanking, equalTo(expectedClientRanking));
}
@Test
public void shouldRankTaskWithUnknownOffsetSumBelowCaughtUpClientAndClientWithLargeLag() {
client1 = EasyMock.createNiceMock(ClientState.class);
client2 = EasyMock.createNiceMock(ClientState.class);
client3 = EasyMock.createNiceMock(ClientState.class);
expect(client1.lagFor(TASK_0_0)).andReturn(UNKNOWN_OFFSET_SUM);
expect(client2.lagFor(TASK_0_0)).andReturn(50L);
expect(client3.lagFor(TASK_0_0)).andReturn(500L);
replay(client1, client2, client3);
final SortedSet<RankedClient<String>> expectedClientRanking = mkSortedSet(
new RankedClient<>(ID_2, 0L),
new RankedClient<>(ID_1, 1L),
new RankedClient<>(ID_3, 500L)
);
final Map<String, ClientState> states = mkMap(
mkEntry(ID_1, client1),
mkEntry(ID_2, client2),
mkEntry(ID_3, client3)
);
final Map<TaskId, SortedSet<RankedClient<String>>> statefulTasksToRankedCandidates =
buildClientRankingsByTask(singleton(TASK_0_0), states, acceptableRecoveryLag);
final SortedSet<RankedClient<String>> clientRanking = statefulTasksToRankedCandidates.get(TASK_0_0);
EasyMock.verify(client1, client2, client3);
assertThat(clientRanking, equalTo(expectedClientRanking));
}
@Test
public void shouldRankAllClientsWithinAcceptableRecoveryLagWithRank0() {
client1 = EasyMock.createNiceMock(ClientState.class);
client2 = EasyMock.createNiceMock(ClientState.class);
expect(client1.lagFor(TASK_0_0)).andReturn(100L);
expect(client2.lagFor(TASK_0_0)).andReturn(0L);
replay(client1, client2);
final SortedSet<RankedClient<String>> expectedClientRanking = mkSortedSet(
new RankedClient<>(ID_1, 0L),
new RankedClient<>(ID_2, 0L)
);
final Map<String, ClientState> states = mkMap(
mkEntry(ID_1, client1),
mkEntry(ID_2, client2)
);
final Map<TaskId, SortedSet<RankedClient<String>>> statefulTasksToRankedCandidates =
buildClientRankingsByTask(singleton(TASK_0_0), states, acceptableRecoveryLag);
EasyMock.verify(client1, client2);
assertThat(statefulTasksToRankedCandidates.get(TASK_0_0), equalTo(expectedClientRanking));
}
@Test
public void shouldRankNotCaughtUpClientsAccordingToLag() {
client1 = EasyMock.createNiceMock(ClientState.class);
client2 = EasyMock.createNiceMock(ClientState.class);
client3 = EasyMock.createNiceMock(ClientState.class);
expect(client1.lagFor(TASK_0_0)).andReturn(900L);
expect(client2.lagFor(TASK_0_0)).andReturn(800L);
expect(client3.lagFor(TASK_0_0)).andReturn(500L);
replay(client1, client2, client3);
final SortedSet<RankedClient<String>> expectedClientRanking = mkSortedSet(
new RankedClient<>(ID_3, 500L),
new RankedClient<>(ID_2, 800L),
new RankedClient<>(ID_1, 900L)
);
final Map<String, ClientState> states = mkMap(
mkEntry(ID_1, client1),
mkEntry(ID_2, client2),
mkEntry(ID_3, client3)
);
final Map<TaskId, SortedSet<RankedClient<String>>> statefulTasksToRankedCandidates =
buildClientRankingsByTask(singleton(TASK_0_0), states, acceptableRecoveryLag);
EasyMock.verify(client1, client2, client3);
assertThat(statefulTasksToRankedCandidates.get(TASK_0_0), equalTo(expectedClientRanking));
}
@Test
public void shouldReturnEmptyClientRankingsWithNoStatefulTasks() {
client1 = EasyMock.createNiceMock(ClientState.class);
client2 = EasyMock.createNiceMock(ClientState.class);
final Map<String, ClientState> states = mkMap(
mkEntry(ID_1, client1),
mkEntry(ID_2, client2)
);
assertTrue(buildClientRankingsByTask(EMPTY_TASKS, states, acceptableRecoveryLag).isEmpty());
}
@Test
public void shouldGetMovementsFromStateConstrainedToBalancedAssignment() {
maxWarmupReplicas = Integer.MAX_VALUE;
final Map<String, List<TaskId>> stateConstrainedAssignment = mkMap(
mkEntry(ID_1, asList(TASK_0_0, TASK_1_2)),
mkEntry(ID_2, asList(TASK_0_1, TASK_1_0)),
mkEntry(ID_3, asList(TASK_0_2, TASK_1_1))
);
final Map<String, List<TaskId>> balancedAssignment = mkMap(
mkEntry(ID_1, asList(TASK_0_0, TASK_1_0)),
mkEntry(ID_2, asList(TASK_0_1, TASK_1_1)),
mkEntry(ID_3, asList(TASK_0_2, TASK_1_2))
);
final Queue<Movement<String>> expectedMovements = new LinkedList<>();
expectedMovements.add(new Movement<>(TASK_1_2, ID_1, ID_3));
expectedMovements.add(new Movement<>(TASK_1_0, ID_2, ID_1));
expectedMovements.add(new Movement<>(TASK_1_1, ID_3, ID_2));
assertThat(getMovements(stateConstrainedAssignment, balancedAssignment, maxWarmupReplicas), equalTo(expectedMovements));
}
@Test
public void shouldOnlyGetUpToMaxWarmupReplicaMovements() {
maxWarmupReplicas = 1;
final Map<String, List<TaskId>> stateConstrainedAssignment = mkMap(
mkEntry(ID_1, asList(TASK_0_0, TASK_1_2)),
mkEntry(ID_2, asList(TASK_0_1, TASK_1_0)),
mkEntry(ID_3, asList(TASK_0_2, TASK_1_1))
);
final Map<String, List<TaskId>> balancedAssignment = mkMap(
mkEntry(ID_1, asList(TASK_0_0, TASK_1_0)),
mkEntry(ID_2, asList(TASK_0_1, TASK_1_1)),
mkEntry(ID_3, asList(TASK_0_2, TASK_1_2))
);
final Queue<Movement<String>> expectedMovements = new LinkedList<>();
expectedMovements.add(new Movement<>(TASK_1_2, ID_1, ID_3));
assertThat(getMovements(stateConstrainedAssignment, balancedAssignment, maxWarmupReplicas), equalTo(expectedMovements));
}
@Test
public void shouldReturnEmptyMovementsWhenPassedEmptyTaskAssignments() {
final Map<String, List<TaskId>> stateConstrainedAssignment = mkMap(
mkEntry(ID_1, emptyList()),
mkEntry(ID_2, emptyList())
);
final Map<String, List<TaskId>> balancedAssignment = mkMap(
mkEntry(ID_1, emptyList()),
mkEntry(ID_2, emptyList())
);
assertTrue(getMovements(stateConstrainedAssignment, balancedAssignment, maxWarmupReplicas).isEmpty());
}
@Test
public void shouldReturnEmptyMovementsWhenPassedIdenticalTaskAssignments() {
final Map<String, List<TaskId>> stateConstrainedAssignment = mkMap(
mkEntry(ID_1, asList(TASK_0_0, TASK_1_0)),
mkEntry(ID_2, asList(TASK_0_1, TASK_1_1))
);
final Map<String, List<TaskId>> balancedAssignment = mkMap(
mkEntry(ID_1, asList(TASK_0_0, TASK_1_0)),
mkEntry(ID_2, asList(TASK_0_1, TASK_1_1))
);
assertTrue(getMovements(stateConstrainedAssignment, balancedAssignment, maxWarmupReplicas).isEmpty());
}
@Test
public void shouldThrowIllegalStateExceptionIfAssignmentsAreOfDifferentSize() {
final Map<String, List<TaskId>> stateConstrainedAssignment = mkMap(
mkEntry(ID_1, asList(TASK_0_0, TASK_0_1))
);
final Map<String, List<TaskId>> balancedAssignment = mkMap(
mkEntry(ID_1, asList(TASK_0_0, TASK_1_0)),
mkEntry(ID_2, asList(TASK_0_1, TASK_1_1))
);
assertThrows(IllegalStateException.class, () -> getMovements(stateConstrainedAssignment, balancedAssignment, maxWarmupReplicas));
}
@Test
public void shouldDecidePreviousAssignmentIsInvalidIfThereAreUnassignedActiveTasks() {
client1 = EasyMock.createNiceMock(ClientState.class);
@ -313,7 +93,7 @@ public class HighAvailabilityTaskAssignorTest {
expect(client1.prevStandbyTasks()).andStubReturn(EMPTY_TASKS);
replay(client1);
allTasks = mkSet(TASK_0_0, TASK_0_1);
clientStates = singletonMap(ID_1, client1);
clientStates = singletonMap(UUID_1, client1);
createTaskAssignor();
assertFalse(taskAssignor.previousAssignmentIsValid());
@ -327,7 +107,7 @@ public class HighAvailabilityTaskAssignorTest {
replay(client1);
allTasks = mkSet(TASK_0_0);
statefulTasks = mkSet(TASK_0_0);
clientStates = singletonMap(ID_1, client1);
clientStates = singletonMap(UUID_1, client1);
numStandbyReplicas = 1;
createTaskAssignor();
@ -350,8 +130,8 @@ public class HighAvailabilityTaskAssignorTest {
allTasks = mkSet(TASK_0_0, TASK_0_1);
statefulTasks = mkSet(TASK_0_0);
clientStates = mkMap(
mkEntry(ID_1, client1),
mkEntry(ID_2, client2)
mkEntry(UUID_1, client1),
mkEntry(UUID_2, client2)
);
createTaskAssignor();
@ -374,8 +154,8 @@ public class HighAvailabilityTaskAssignorTest {
allTasks = mkSet(TASK_0_0, TASK_0_1);
statefulTasks = mkSet(TASK_0_0);
clientStates = mkMap(
mkEntry(ID_1, client1),
mkEntry(ID_2, client2)
mkEntry(UUID_1, client1),
mkEntry(UUID_2, client2)
);
createTaskAssignor();
@ -389,10 +169,10 @@ public class HighAvailabilityTaskAssignorTest {
replay(client1);
allTasks = mkSet(TASK_0_0);
statefulTasks = mkSet(TASK_0_0);
clientStates = singletonMap(ID_1, client1);
clientStates = singletonMap(UUID_1, client1);
createTaskAssignor();
assertTrue(taskAssignor.taskIsCaughtUpOnClient(TASK_0_0, ID_1));
assertTrue(taskAssignor.taskIsCaughtUpOnClient(TASK_0_0, UUID_1));
}
@Test
@ -401,11 +181,11 @@ public class HighAvailabilityTaskAssignorTest {
expect(client1.lagFor(TASK_0_0)).andReturn(0L);
allTasks = mkSet(TASK_0_0);
statefulTasks = mkSet(TASK_0_0);
clientStates = singletonMap(ID_1, client1);
clientStates = singletonMap(UUID_1, client1);
replay(client1);
createTaskAssignor();
assertTrue(taskAssignor.taskIsCaughtUpOnClient(TASK_0_0, ID_1));
assertTrue(taskAssignor.taskIsCaughtUpOnClient(TASK_0_0, UUID_1));
}
@Test
@ -418,12 +198,12 @@ public class HighAvailabilityTaskAssignorTest {
allTasks = mkSet(TASK_0_0);
statefulTasks = mkSet(TASK_0_0);
clientStates = mkMap(
mkEntry(ID_1, client1),
mkEntry(ID_2, client2)
mkEntry(UUID_1, client1),
mkEntry(UUID_2, client2)
);
createTaskAssignor();
assertFalse(taskAssignor.taskIsCaughtUpOnClient(TASK_0_0, ID_1));
assertFalse(taskAssignor.taskIsCaughtUpOnClient(TASK_0_0, UUID_1));
}
@Test
@ -432,8 +212,8 @@ public class HighAvailabilityTaskAssignorTest {
client2 = EasyMock.createNiceMock(ClientState.class);
client3 = EasyMock.createNiceMock(ClientState.class);
final Set<ClientState> states = mkSet(client1, client2, client3);
final Set<TaskId> statefulTasks = mkSet(TASK_0_0, TASK_0_1, TASK_0_2, TASK_0_3, TASK_1_0, TASK_1_1, TASK_2_0,
TASK_2_1, TASK_2_3);
final Set<TaskId> statefulTasks =
mkSet(TASK_0_0, TASK_0_1, TASK_0_2, TASK_0_3, TASK_1_0, TASK_1_1, TASK_2_0, TASK_2_1, TASK_2_3);
expect(client1.capacity()).andReturn(1);
expect(client1.prevActiveTasks()).andReturn(mkSet(TASK_0_0, TASK_0_1, TASK_0_2, TASK_0_3));
@ -454,8 +234,8 @@ public class HighAvailabilityTaskAssignorTest {
client2 = EasyMock.createNiceMock(ClientState.class);
client3 = EasyMock.createNiceMock(ClientState.class);
final Set<ClientState> states = mkSet(client1, client2, client3);
final Set<TaskId> statefulTasks = mkSet(TASK_0_0, TASK_0_1, TASK_0_2, TASK_0_3, TASK_1_0, TASK_1_1, TASK_2_0,
TASK_2_1, TASK_2_3);
final Set<TaskId> statefulTasks =
mkSet(TASK_0_0, TASK_0_1, TASK_0_2, TASK_0_3, TASK_1_0, TASK_1_1, TASK_2_0, TASK_2_1, TASK_2_3);
// client 1: 4 tasks per thread
expect(client1.capacity()).andReturn(1);
@ -729,16 +509,16 @@ public class HighAvailabilityTaskAssignorTest {
assertThat(client2.standbyTaskCount(), equalTo(1));
}
private Map<String, ClientState> getClientStatesWithOneClient() {
return singletonMap(ID_1, client1);
private Map<UUID, ClientState> getClientStatesWithOneClient() {
return singletonMap(UUID_1, client1);
}
private Map<String, ClientState> getClientStatesWithTwoClients() {
return mkMap(mkEntry(ID_1, client1), mkEntry(ID_2, client2));
private Map<UUID, ClientState> getClientStatesWithTwoClients() {
return mkMap(mkEntry(UUID_1, client1), mkEntry(UUID_2, client2));
}
private Map<String, ClientState> getClientStatesWithThreeClients() {
return mkMap(mkEntry(ID_1, client1), mkEntry(ID_2, client2), mkEntry(ID_3, client3));
private Map<UUID, ClientState> getClientStatesWithThreeClients() {
return mkMap(mkEntry(UUID_1, client1), mkEntry(UUID_2, client2), mkEntry(UUID_3, client3));
}
private static void assertHasNoActiveTasks(final ClientState... clients) {

View File

@ -0,0 +1,163 @@
/*
* 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.kafka.streams.processor.internals.assignment;
import static java.util.Collections.emptySet;
import static java.util.Collections.singleton;
import static org.apache.kafka.common.utils.Utils.mkEntry;
import static org.apache.kafka.common.utils.Utils.mkMap;
import static org.apache.kafka.common.utils.Utils.mkSortedSet;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_0_0;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.UUID_1;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.UUID_2;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.UUID_3;
import static org.apache.kafka.streams.processor.internals.assignment.RankedClient.buildClientRankingsByTask;
import static org.apache.kafka.streams.processor.internals.assignment.SubscriptionInfo.UNKNOWN_OFFSET_SUM;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertTrue;
import java.util.Map;
import java.util.SortedSet;
import java.util.UUID;
import org.apache.kafka.streams.processor.TaskId;
import org.apache.kafka.streams.processor.internals.Task;
import org.easymock.EasyMock;
import org.junit.Test;
public class RankedClientTest {
private static final long ACCEPTABLE_RECOVERY_LAG = 100L;
private ClientState client1 = EasyMock.createNiceMock(ClientState.class);
private ClientState client2 = EasyMock.createNiceMock(ClientState.class);
private ClientState client3 = EasyMock.createNiceMock(ClientState.class);
@Test
public void shouldRankPreviousClientAboveEquallyCaughtUpClient() {
expect(client1.lagFor(TASK_0_0)).andReturn(Task.LATEST_OFFSET);
expect(client2.lagFor(TASK_0_0)).andReturn(0L);
replay(client1, client2);
final SortedSet<RankedClient> expectedClientRanking = mkSortedSet(
new RankedClient(UUID_1, Task.LATEST_OFFSET),
new RankedClient(UUID_2, 0L)
);
final Map<UUID, ClientState> states = mkMap(
mkEntry(UUID_1, client1),
mkEntry(UUID_2, client2)
);
final Map<TaskId, SortedSet<RankedClient>> statefulTasksToRankedCandidates =
buildClientRankingsByTask(singleton(TASK_0_0), states, ACCEPTABLE_RECOVERY_LAG);
final SortedSet<RankedClient> clientRanking = statefulTasksToRankedCandidates.get(TASK_0_0);
EasyMock.verify(client1, client2);
assertThat(clientRanking, equalTo(expectedClientRanking));
}
@Test
public void shouldRankTaskWithUnknownOffsetSumBelowCaughtUpClientAndClientWithLargeLag() {
expect(client1.lagFor(TASK_0_0)).andReturn(UNKNOWN_OFFSET_SUM);
expect(client2.lagFor(TASK_0_0)).andReturn(50L);
expect(client3.lagFor(TASK_0_0)).andReturn(500L);
replay(client1, client2, client3);
final SortedSet<RankedClient> expectedClientRanking = mkSortedSet(
new RankedClient(UUID_2, 0L),
new RankedClient(UUID_1, 1L),
new RankedClient(UUID_3, 500L)
);
final Map<UUID, ClientState> states = mkMap(
mkEntry(UUID_1, client1),
mkEntry(UUID_2, client2),
mkEntry(UUID_3, client3)
);
final Map<TaskId, SortedSet<RankedClient>> statefulTasksToRankedCandidates =
buildClientRankingsByTask(singleton(TASK_0_0), states, ACCEPTABLE_RECOVERY_LAG);
final SortedSet<RankedClient> clientRanking = statefulTasksToRankedCandidates.get(TASK_0_0);
EasyMock.verify(client1, client2, client3);
assertThat(clientRanking, equalTo(expectedClientRanking));
}
@Test
public void shouldRankAllClientsWithinAcceptableRecoveryLagWithRank0() {
expect(client1.lagFor(TASK_0_0)).andReturn(100L);
expect(client2.lagFor(TASK_0_0)).andReturn(0L);
replay(client1, client2);
final SortedSet<RankedClient> expectedClientRanking = mkSortedSet(
new RankedClient(UUID_1, 0L),
new RankedClient(UUID_2, 0L)
);
final Map<UUID, ClientState> states = mkMap(
mkEntry(UUID_1, client1),
mkEntry(UUID_2, client2)
);
final Map<TaskId, SortedSet<RankedClient>> statefulTasksToRankedCandidates =
buildClientRankingsByTask(singleton(TASK_0_0), states, ACCEPTABLE_RECOVERY_LAG);
EasyMock.verify(client1, client2);
assertThat(statefulTasksToRankedCandidates.get(TASK_0_0), equalTo(expectedClientRanking));
}
@Test
public void shouldRankNotCaughtUpClientsAccordingToLag() {
expect(client1.lagFor(TASK_0_0)).andReturn(900L);
expect(client2.lagFor(TASK_0_0)).andReturn(800L);
expect(client3.lagFor(TASK_0_0)).andReturn(500L);
replay(client1, client2, client3);
final SortedSet<RankedClient> expectedClientRanking = mkSortedSet(
new RankedClient(UUID_3, 500L),
new RankedClient(UUID_2, 800L),
new RankedClient(UUID_1, 900L)
);
final Map<UUID, ClientState> states = mkMap(
mkEntry(UUID_1, client1),
mkEntry(UUID_2, client2),
mkEntry(UUID_3, client3)
);
final Map<TaskId, SortedSet<RankedClient>> statefulTasksToRankedCandidates =
buildClientRankingsByTask(singleton(TASK_0_0), states, ACCEPTABLE_RECOVERY_LAG);
EasyMock.verify(client1, client2, client3);
assertThat(statefulTasksToRankedCandidates.get(TASK_0_0), equalTo(expectedClientRanking));
}
@Test
public void shouldReturnEmptyClientRankingsWithNoStatefulTasks() {
final Map<UUID, ClientState> states = mkMap(
mkEntry(UUID_1, client1),
mkEntry(UUID_2, client2)
);
assertTrue(buildClientRankingsByTask(emptySet(), states, ACCEPTABLE_RECOVERY_LAG).isEmpty());
}
}

View File

@ -16,13 +16,13 @@
*/
package org.apache.kafka.streams.processor.internals.assignment;
import java.util.UUID;
import org.apache.kafka.common.utils.Utils;
import org.apache.kafka.streams.processor.TaskId;
import org.apache.kafka.streams.processor.internals.assignment.AssignorConfiguration.AssignmentConfigs;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@ -32,6 +32,28 @@ import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import static java.util.Arrays.asList;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_0_0;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_0_1;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_0_2;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_0_3;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_0_4;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_0_5;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_0_6;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_1_0;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_1_1;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_1_2;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_1_3;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_2_0;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_2_1;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_2_2;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_2_3;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.UUID_1;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.UUID_2;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.UUID_3;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.UUID_4;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.UUID_5;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.UUID_6;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.IsIterableContaining.hasItem;
@ -41,302 +63,281 @@ import static org.junit.Assert.assertTrue;
public class StickyTaskAssignorTest {
private final TaskId task00 = new TaskId(0, 0);
private final TaskId task01 = new TaskId(0, 1);
private final TaskId task02 = new TaskId(0, 2);
private final TaskId task03 = new TaskId(0, 3);
private final TaskId task04 = new TaskId(0, 4);
private final TaskId task05 = new TaskId(0, 5);
private final List<Integer> expectedTopicGroupIds = asList(1, 2);
private final TaskId task10 = new TaskId(1, 0);
private final TaskId task11 = new TaskId(1, 1);
private final TaskId task12 = new TaskId(1, 2);
private final TaskId task20 = new TaskId(2, 0);
private final TaskId task21 = new TaskId(2, 1);
private final TaskId task22 = new TaskId(2, 2);
private final List<Integer> expectedTopicGroupIds = Arrays.asList(1, 2);
private final Map<Integer, ClientState> clients = new TreeMap<>();
private final Integer p1 = 1;
private final Integer p2 = 2;
private final Integer p3 = 3;
private final Integer p4 = 4;
private final Map<UUID, ClientState> clients = new TreeMap<>();
@Test
public void shouldAssignOneActiveTaskToEachProcessWhenTaskCountSameAsProcessCount() {
createClient(p1, 1);
createClient(p2, 1);
createClient(p3, 1);
createClient(UUID_1, 1);
createClient(UUID_2, 1);
createClient(UUID_3, 1);
final StickyTaskAssignor taskAssignor = createTaskAssignor(task00, task01, task02);
final StickyTaskAssignor taskAssignor = createTaskAssignor(TASK_0_0, TASK_0_1, TASK_0_2);
taskAssignor.assign();
for (final Integer processId : clients.keySet()) {
for (final UUID processId : clients.keySet()) {
assertThat(clients.get(processId).activeTaskCount(), equalTo(1));
}
}
@Test
public void shouldAssignTopicGroupIdEvenlyAcrossClientsWithNoStandByTasks() {
createClient(p1, 2);
createClient(p2, 2);
createClient(p3, 2);
createClient(UUID_1, 2);
createClient(UUID_2, 2);
createClient(UUID_3, 2);
final StickyTaskAssignor taskAssignor = createTaskAssignor(task10, task11, task22, task20, task21, task12);
final StickyTaskAssignor taskAssignor = createTaskAssignor(TASK_1_0, TASK_1_1, TASK_2_2, TASK_2_0, TASK_2_1, TASK_1_2);
taskAssignor.assign();
assertActiveTaskTopicGroupIdsEvenlyDistributed();
}
@Test
public void shouldAssignTopicGroupIdEvenlyAcrossClientsWithStandByTasks() {
createClient(p1, 2);
createClient(p2, 2);
createClient(p3, 2);
createClient(UUID_1, 2);
createClient(UUID_2, 2);
createClient(UUID_3, 2);
final StickyTaskAssignor taskAssignor = createTaskAssignor(1, task20, task11, task12, task10, task21, task22);
final StickyTaskAssignor taskAssignor = createTaskAssignor(1, TASK_2_0, TASK_1_1, TASK_1_2, TASK_1_0, TASK_2_1, TASK_2_2);
taskAssignor.assign();
assertActiveTaskTopicGroupIdsEvenlyDistributed();
}
@Test
public void shouldNotMigrateActiveTaskToOtherProcess() {
createClientWithPreviousActiveTasks(p1, 1, task00);
createClientWithPreviousActiveTasks(p2, 1, task01);
createClientWithPreviousActiveTasks(UUID_1, 1, TASK_0_0);
createClientWithPreviousActiveTasks(UUID_2, 1, TASK_0_1);
final StickyTaskAssignor firstAssignor = createTaskAssignor(task00, task01, task02);
final StickyTaskAssignor firstAssignor = createTaskAssignor(TASK_0_0, TASK_0_1, TASK_0_2);
firstAssignor.assign();
assertThat(clients.get(p1).activeTasks(), hasItems(task00));
assertThat(clients.get(p2).activeTasks(), hasItems(task01));
assertThat(allActiveTasks(), equalTo(Arrays.asList(task00, task01, task02)));
assertThat(clients.get(UUID_1).activeTasks(), hasItems(TASK_0_0));
assertThat(clients.get(UUID_2).activeTasks(), hasItems(TASK_0_1));
assertThat(allActiveTasks(), equalTo(asList(TASK_0_0, TASK_0_1, TASK_0_2)));
clients.clear();
// flip the previous active tasks assignment around.
createClientWithPreviousActiveTasks(p1, 1, task01);
createClientWithPreviousActiveTasks(p2, 1, task02);
createClientWithPreviousActiveTasks(UUID_1, 1, TASK_0_1);
createClientWithPreviousActiveTasks(UUID_2, 1, TASK_0_2);
final StickyTaskAssignor secondAssignor = createTaskAssignor(task00, task01, task02);
final StickyTaskAssignor secondAssignor = createTaskAssignor(TASK_0_0, TASK_0_1, TASK_0_2);
secondAssignor.assign();
assertThat(clients.get(p1).activeTasks(), hasItems(task01));
assertThat(clients.get(p2).activeTasks(), hasItems(task02));
assertThat(allActiveTasks(), equalTo(Arrays.asList(task00, task01, task02)));
assertThat(clients.get(UUID_1).activeTasks(), hasItems(TASK_0_1));
assertThat(clients.get(UUID_2).activeTasks(), hasItems(TASK_0_2));
assertThat(allActiveTasks(), equalTo(asList(TASK_0_0, TASK_0_1, TASK_0_2)));
}
@Test
public void shouldMigrateActiveTasksToNewProcessWithoutChangingAllAssignments() {
createClientWithPreviousActiveTasks(p1, 1, task00, task02);
createClientWithPreviousActiveTasks(p2, 1, task01);
createClient(p3, 1);
createClientWithPreviousActiveTasks(UUID_1, 1, TASK_0_0, TASK_0_2);
createClientWithPreviousActiveTasks(UUID_2, 1, TASK_0_1);
createClient(UUID_3, 1);
final StickyTaskAssignor taskAssignor = createTaskAssignor(task00, task01, task02);
final StickyTaskAssignor taskAssignor = createTaskAssignor(TASK_0_0, TASK_0_1, TASK_0_2);
taskAssignor.assign();
assertThat(clients.get(p2).activeTasks(), equalTo(Collections.singleton(task01)));
assertThat(clients.get(p1).activeTasks().size(), equalTo(1));
assertThat(clients.get(p3).activeTasks().size(), equalTo(1));
assertThat(allActiveTasks(), equalTo(Arrays.asList(task00, task01, task02)));
assertThat(clients.get(UUID_2).activeTasks(), equalTo(Collections.singleton(TASK_0_1)));
assertThat(clients.get(UUID_1).activeTasks().size(), equalTo(1));
assertThat(clients.get(UUID_3).activeTasks().size(), equalTo(1));
assertThat(allActiveTasks(), equalTo(asList(TASK_0_0, TASK_0_1, TASK_0_2)));
}
@Test
public void shouldAssignBasedOnCapacity() {
createClient(p1, 1);
createClient(p2, 2);
final StickyTaskAssignor taskAssignor = createTaskAssignor(task00, task01, task02);
createClient(UUID_1, 1);
createClient(UUID_2, 2);
final StickyTaskAssignor taskAssignor = createTaskAssignor(TASK_0_0, TASK_0_1, TASK_0_2);
taskAssignor.assign();
assertThat(clients.get(p1).activeTasks().size(), equalTo(1));
assertThat(clients.get(p2).activeTasks().size(), equalTo(2));
assertThat(clients.get(UUID_1).activeTasks().size(), equalTo(1));
assertThat(clients.get(UUID_2).activeTasks().size(), equalTo(2));
}
@Test
public void shouldAssignTasksEvenlyWithUnequalTopicGroupSizes() {
createClientWithPreviousActiveTasks(UUID_1, 1, TASK_0_0, TASK_0_1, TASK_0_2, TASK_0_3, TASK_0_4, TASK_0_5, TASK_1_0);
createClientWithPreviousActiveTasks(p1, 1, task00, task01, task02, task03,
task04, task05, task10);
createClient(UUID_2, 1);
createClient(p2, 1);
final StickyTaskAssignor taskAssignor = createTaskAssignor(TASK_1_0, TASK_0_0, TASK_0_1, TASK_0_2, TASK_0_3, TASK_0_4, TASK_0_5);
final StickyTaskAssignor taskAssignor = createTaskAssignor(task10, task00, task01, task02, task03, task04, task05);
final Set<TaskId> expectedClientITasks = new HashSet<>(Arrays.asList(task00, task01, task10, task05));
final Set<TaskId> expectedClientIITasks = new HashSet<>(Arrays.asList(task02, task03, task04));
final Set<TaskId> expectedClientITasks = new HashSet<>(asList(TASK_0_0, TASK_0_1, TASK_1_0, TASK_0_5));
final Set<TaskId> expectedClientIITasks = new HashSet<>(asList(TASK_0_2, TASK_0_3, TASK_0_4));
taskAssignor.assign();
assertThat(clients.get(p1).activeTasks(), equalTo(expectedClientITasks));
assertThat(clients.get(p2).activeTasks(), equalTo(expectedClientIITasks));
assertThat(clients.get(UUID_1).activeTasks(), equalTo(expectedClientITasks));
assertThat(clients.get(UUID_2).activeTasks(), equalTo(expectedClientIITasks));
}
@Test
public void shouldKeepActiveTaskStickynessWhenMoreClientThanActiveTasks() {
final int p5 = 5;
createClientWithPreviousActiveTasks(p1, 1, task00);
createClientWithPreviousActiveTasks(p2, 1, task02);
createClientWithPreviousActiveTasks(p3, 1, task01);
createClient(p4, 1);
createClient(p5, 1);
createClientWithPreviousActiveTasks(UUID_1, 1, TASK_0_0);
createClientWithPreviousActiveTasks(UUID_2, 1, TASK_0_2);
createClientWithPreviousActiveTasks(UUID_3, 1, TASK_0_1);
createClient(UUID_4, 1);
createClient(UUID_5, 1);
final StickyTaskAssignor taskAssignor = createTaskAssignor(task00, task01, task02);
final StickyTaskAssignor taskAssignor = createTaskAssignor(TASK_0_0, TASK_0_1, TASK_0_2);
taskAssignor.assign();
assertThat(clients.get(p1).activeTasks(), equalTo(Collections.singleton(task00)));
assertThat(clients.get(p2).activeTasks(), equalTo(Collections.singleton(task02)));
assertThat(clients.get(p3).activeTasks(), equalTo(Collections.singleton(task01)));
assertThat(clients.get(UUID_1).activeTasks(), equalTo(Collections.singleton(TASK_0_0)));
assertThat(clients.get(UUID_2).activeTasks(), equalTo(Collections.singleton(TASK_0_2)));
assertThat(clients.get(UUID_3).activeTasks(), equalTo(Collections.singleton(TASK_0_1)));
// change up the assignment and make sure it is still sticky
clients.clear();
createClient(p1, 1);
createClientWithPreviousActiveTasks(p2, 1, task00);
createClient(p3, 1);
createClientWithPreviousActiveTasks(p4, 1, task02);
createClientWithPreviousActiveTasks(p5, 1, task01);
createClient(UUID_1, 1);
createClientWithPreviousActiveTasks(UUID_2, 1, TASK_0_0);
createClient(UUID_3, 1);
createClientWithPreviousActiveTasks(UUID_4, 1, TASK_0_2);
createClientWithPreviousActiveTasks(UUID_5, 1, TASK_0_1);
final StickyTaskAssignor secondAssignor = createTaskAssignor(task00, task01, task02);
final StickyTaskAssignor secondAssignor = createTaskAssignor(TASK_0_0, TASK_0_1, TASK_0_2);
secondAssignor.assign();
assertThat(clients.get(p2).activeTasks(), equalTo(Collections.singleton(task00)));
assertThat(clients.get(p4).activeTasks(), equalTo(Collections.singleton(task02)));
assertThat(clients.get(p5).activeTasks(), equalTo(Collections.singleton(task01)));
assertThat(clients.get(UUID_2).activeTasks(), equalTo(Collections.singleton(TASK_0_0)));
assertThat(clients.get(UUID_4).activeTasks(), equalTo(Collections.singleton(TASK_0_2)));
assertThat(clients.get(UUID_5).activeTasks(), equalTo(Collections.singleton(TASK_0_1)));
}
@Test
public void shouldAssignTasksToClientWithPreviousStandbyTasks() {
final ClientState client1 = createClient(p1, 1);
client1.addPreviousStandbyTasks(Utils.mkSet(task02));
final ClientState client2 = createClient(p2, 1);
client2.addPreviousStandbyTasks(Utils.mkSet(task01));
final ClientState client3 = createClient(p3, 1);
client3.addPreviousStandbyTasks(Utils.mkSet(task00));
final ClientState client1 = createClient(UUID_1, 1);
client1.addPreviousStandbyTasks(Utils.mkSet(TASK_0_2));
final ClientState client2 = createClient(UUID_2, 1);
client2.addPreviousStandbyTasks(Utils.mkSet(TASK_0_1));
final ClientState client3 = createClient(UUID_3, 1);
client3.addPreviousStandbyTasks(Utils.mkSet(TASK_0_0));
final StickyTaskAssignor taskAssignor = createTaskAssignor(task00, task01, task02);
final StickyTaskAssignor taskAssignor = createTaskAssignor(TASK_0_0, TASK_0_1, TASK_0_2);
taskAssignor.assign();
assertThat(clients.get(p1).activeTasks(), equalTo(Collections.singleton(task02)));
assertThat(clients.get(p2).activeTasks(), equalTo(Collections.singleton(task01)));
assertThat(clients.get(p3).activeTasks(), equalTo(Collections.singleton(task00)));
assertThat(clients.get(UUID_1).activeTasks(), equalTo(Collections.singleton(TASK_0_2)));
assertThat(clients.get(UUID_2).activeTasks(), equalTo(Collections.singleton(TASK_0_1)));
assertThat(clients.get(UUID_3).activeTasks(), equalTo(Collections.singleton(TASK_0_0)));
}
@Test
public void shouldAssignBasedOnCapacityWhenMultipleClientHaveStandbyTasks() {
final ClientState c1 = createClientWithPreviousActiveTasks(p1, 1, task00);
c1.addPreviousStandbyTasks(Utils.mkSet(task01));
final ClientState c2 = createClientWithPreviousActiveTasks(p2, 2, task02);
c2.addPreviousStandbyTasks(Utils.mkSet(task01));
final ClientState c1 = createClientWithPreviousActiveTasks(UUID_1, 1, TASK_0_0);
c1.addPreviousStandbyTasks(Utils.mkSet(TASK_0_1));
final ClientState c2 = createClientWithPreviousActiveTasks(UUID_2, 2, TASK_0_2);
c2.addPreviousStandbyTasks(Utils.mkSet(TASK_0_1));
final StickyTaskAssignor taskAssignor = createTaskAssignor(task00, task01, task02);
final StickyTaskAssignor taskAssignor = createTaskAssignor(TASK_0_0, TASK_0_1, TASK_0_2);
taskAssignor.assign();
assertThat(clients.get(p1).activeTasks(), equalTo(Collections.singleton(task00)));
assertThat(clients.get(p2).activeTasks(), equalTo(Utils.mkSet(task02, task01)));
assertThat(clients.get(UUID_1).activeTasks(), equalTo(Collections.singleton(TASK_0_0)));
assertThat(clients.get(UUID_2).activeTasks(), equalTo(Utils.mkSet(TASK_0_2, TASK_0_1)));
}
@Test
public void shouldAssignStandbyTasksToDifferentClientThanCorrespondingActiveTaskIsAssingedTo() {
createClientWithPreviousActiveTasks(p1, 1, task00);
createClientWithPreviousActiveTasks(p2, 1, task01);
createClientWithPreviousActiveTasks(p3, 1, task02);
createClientWithPreviousActiveTasks(p4, 1, task03);
createClientWithPreviousActiveTasks(UUID_1, 1, TASK_0_0);
createClientWithPreviousActiveTasks(UUID_2, 1, TASK_0_1);
createClientWithPreviousActiveTasks(UUID_3, 1, TASK_0_2);
createClientWithPreviousActiveTasks(UUID_4, 1, TASK_0_3);
final StickyTaskAssignor taskAssignor = createTaskAssignor(1, task00, task01, task02, task03);
final StickyTaskAssignor taskAssignor = createTaskAssignor(1, TASK_0_0, TASK_0_1, TASK_0_2, TASK_0_3);
taskAssignor.assign();
assertThat(clients.get(p1).standbyTasks(), not(hasItems(task00)));
assertTrue(clients.get(p1).standbyTasks().size() <= 2);
assertThat(clients.get(p2).standbyTasks(), not(hasItems(task01)));
assertTrue(clients.get(p2).standbyTasks().size() <= 2);
assertThat(clients.get(p3).standbyTasks(), not(hasItems(task02)));
assertTrue(clients.get(p3).standbyTasks().size() <= 2);
assertThat(clients.get(p4).standbyTasks(), not(hasItems(task03)));
assertTrue(clients.get(p4).standbyTasks().size() <= 2);
assertThat(clients.get(UUID_1).standbyTasks(), not(hasItems(TASK_0_0)));
assertTrue(clients.get(UUID_1).standbyTasks().size() <= 2);
assertThat(clients.get(UUID_2).standbyTasks(), not(hasItems(TASK_0_1)));
assertTrue(clients.get(UUID_2).standbyTasks().size() <= 2);
assertThat(clients.get(UUID_3).standbyTasks(), not(hasItems(TASK_0_2)));
assertTrue(clients.get(UUID_3).standbyTasks().size() <= 2);
assertThat(clients.get(UUID_4).standbyTasks(), not(hasItems(TASK_0_3)));
assertTrue(clients.get(UUID_4).standbyTasks().size() <= 2);
int nonEmptyStandbyTaskCount = 0;
for (final Integer client : clients.keySet()) {
for (final UUID client : clients.keySet()) {
nonEmptyStandbyTaskCount += clients.get(client).standbyTasks().isEmpty() ? 0 : 1;
}
assertTrue(nonEmptyStandbyTaskCount >= 3);
assertThat(allStandbyTasks(), equalTo(Arrays.asList(task00, task01, task02, task03)));
assertThat(allStandbyTasks(), equalTo(asList(TASK_0_0, TASK_0_1, TASK_0_2, TASK_0_3)));
}
@Test
public void shouldAssignMultipleReplicasOfStandbyTask() {
createClientWithPreviousActiveTasks(p1, 1, task00);
createClientWithPreviousActiveTasks(p2, 1, task01);
createClientWithPreviousActiveTasks(p3, 1, task02);
createClientWithPreviousActiveTasks(UUID_1, 1, TASK_0_0);
createClientWithPreviousActiveTasks(UUID_2, 1, TASK_0_1);
createClientWithPreviousActiveTasks(UUID_3, 1, TASK_0_2);
final StickyTaskAssignor taskAssignor = createTaskAssignor(2, task00, task01, task02);
final StickyTaskAssignor taskAssignor = createTaskAssignor(2, TASK_0_0, TASK_0_1, TASK_0_2);
taskAssignor.assign();
assertThat(clients.get(p1).standbyTasks(), equalTo(Utils.mkSet(task01, task02)));
assertThat(clients.get(p2).standbyTasks(), equalTo(Utils.mkSet(task02, task00)));
assertThat(clients.get(p3).standbyTasks(), equalTo(Utils.mkSet(task00, task01)));
assertThat(clients.get(UUID_1).standbyTasks(), equalTo(Utils.mkSet(TASK_0_1, TASK_0_2)));
assertThat(clients.get(UUID_2).standbyTasks(), equalTo(Utils.mkSet(TASK_0_2, TASK_0_0)));
assertThat(clients.get(UUID_3).standbyTasks(), equalTo(Utils.mkSet(TASK_0_0, TASK_0_1)));
}
@Test
public void shouldNotAssignStandbyTaskReplicasWhenNoClientAvailableWithoutHavingTheTaskAssigned() {
createClient(p1, 1);
final StickyTaskAssignor taskAssignor = createTaskAssignor(1, task00);
createClient(UUID_1, 1);
final StickyTaskAssignor taskAssignor = createTaskAssignor(1, TASK_0_0);
taskAssignor.assign();
assertThat(clients.get(p1).standbyTasks().size(), equalTo(0));
assertThat(clients.get(UUID_1).standbyTasks().size(), equalTo(0));
}
@Test
public void shouldAssignActiveAndStandbyTasks() {
createClient(p1, 1);
createClient(p2, 1);
createClient(p3, 1);
createClient(UUID_1, 1);
createClient(UUID_2, 1);
createClient(UUID_3, 1);
final StickyTaskAssignor<Integer> taskAssignor = createTaskAssignor(1, task00, task01, task02);
final StickyTaskAssignor taskAssignor = createTaskAssignor(1, TASK_0_0, TASK_0_1, TASK_0_2);
taskAssignor.assign();
assertThat(allActiveTasks(), equalTo(Arrays.asList(task00, task01, task02)));
assertThat(allStandbyTasks(), equalTo(Arrays.asList(task00, task01, task02)));
assertThat(allActiveTasks(), equalTo(asList(TASK_0_0, TASK_0_1, TASK_0_2)));
assertThat(allStandbyTasks(), equalTo(asList(TASK_0_0, TASK_0_1, TASK_0_2)));
}
@Test
public void shouldAssignAtLeastOneTaskToEachClientIfPossible() {
createClient(p1, 3);
createClient(p2, 1);
createClient(p3, 1);
createClient(UUID_1, 3);
createClient(UUID_2, 1);
createClient(UUID_3, 1);
final StickyTaskAssignor<Integer> taskAssignor = createTaskAssignor(task00, task01, task02);
final StickyTaskAssignor taskAssignor = createTaskAssignor(TASK_0_0, TASK_0_1, TASK_0_2);
taskAssignor.assign();
assertThat(clients.get(p1).assignedTaskCount(), equalTo(1));
assertThat(clients.get(p2).assignedTaskCount(), equalTo(1));
assertThat(clients.get(p3).assignedTaskCount(), equalTo(1));
assertThat(clients.get(UUID_1).assignedTaskCount(), equalTo(1));
assertThat(clients.get(UUID_2).assignedTaskCount(), equalTo(1));
assertThat(clients.get(UUID_3).assignedTaskCount(), equalTo(1));
}
@Test
public void shouldAssignEachActiveTaskToOneClientWhenMoreClientsThanTasks() {
createClient(p1, 1);
createClient(p2, 1);
createClient(p3, 1);
createClient(p4, 1);
createClient(5, 1);
createClient(6, 1);
createClient(UUID_1, 1);
createClient(UUID_2, 1);
createClient(UUID_3, 1);
createClient(UUID_4, 1);
createClient(UUID_5, 1);
createClient(UUID_6, 1);
final StickyTaskAssignor<Integer> taskAssignor = createTaskAssignor(task00, task01, task02);
final StickyTaskAssignor taskAssignor = createTaskAssignor(TASK_0_0, TASK_0_1, TASK_0_2);
taskAssignor.assign();
assertThat(allActiveTasks(), equalTo(Arrays.asList(task00, task01, task02)));
assertThat(allActiveTasks(), equalTo(asList(TASK_0_0, TASK_0_1, TASK_0_2)));
}
@Test
public void shouldBalanceActiveAndStandbyTasksAcrossAvailableClients() {
createClient(p1, 1);
createClient(p2, 1);
createClient(p3, 1);
createClient(p4, 1);
createClient(5, 1);
createClient(6, 1);
createClient(UUID_1, 1);
createClient(UUID_2, 1);
createClient(UUID_3, 1);
createClient(UUID_4, 1);
createClient(UUID_5, 1);
createClient(UUID_6, 1);
final StickyTaskAssignor<Integer> taskAssignor = createTaskAssignor(1, task00, task01, task02);
final StickyTaskAssignor taskAssignor = createTaskAssignor(1, TASK_0_0, TASK_0_1, TASK_0_2);
taskAssignor.assign();
for (final ClientState clientState : clients.values()) {
@ -346,12 +347,12 @@ public class StickyTaskAssignorTest {
@Test
public void shouldAssignMoreTasksToClientWithMoreCapacity() {
createClient(p2, 2);
createClient(p1, 1);
createClient(UUID_2, 2);
createClient(UUID_1, 1);
final StickyTaskAssignor<Integer> taskAssignor = createTaskAssignor(task00,
task01,
task02,
final StickyTaskAssignor taskAssignor = createTaskAssignor(TASK_0_0,
TASK_0_1,
TASK_0_2,
new TaskId(1, 0),
new TaskId(1, 1),
new TaskId(1, 2),
@ -363,16 +364,16 @@ public class StickyTaskAssignorTest {
new TaskId(3, 2));
taskAssignor.assign();
assertThat(clients.get(p2).assignedTaskCount(), equalTo(8));
assertThat(clients.get(p1).assignedTaskCount(), equalTo(4));
assertThat(clients.get(UUID_2).assignedTaskCount(), equalTo(8));
assertThat(clients.get(UUID_1).assignedTaskCount(), equalTo(4));
}
@Test
public void shouldEvenlyDistributeByTaskIdAndPartition() {
createClient(p1, 4);
createClient(p2, 4);
createClient(p3, 4);
createClient(p4, 4);
createClient(UUID_1, 4);
createClient(UUID_2, 4);
createClient(UUID_3, 4);
createClient(UUID_4, 4);
final List<TaskId> taskIds = new ArrayList<>();
final TaskId[] taskIdArray = new TaskId[16];
@ -386,7 +387,7 @@ public class StickyTaskAssignorTest {
Collections.shuffle(taskIds);
taskIds.toArray(taskIdArray);
final StickyTaskAssignor<Integer> taskAssignor = createTaskAssignor(taskIdArray);
final StickyTaskAssignor taskAssignor = createTaskAssignor(taskIdArray);
taskAssignor.assign();
Collections.sort(taskIds);
@ -395,29 +396,30 @@ public class StickyTaskAssignorTest {
final Set<TaskId> expectedClientThreeAssignment = getExpectedTaskIdAssignment(taskIds, 2, 6, 10, 14);
final Set<TaskId> expectedClientFourAssignment = getExpectedTaskIdAssignment(taskIds, 3, 7, 11, 15);
final Map<Integer, Set<TaskId>> sortedAssignments = sortClientAssignments(clients);
final Map<UUID, Set<TaskId>> sortedAssignments = sortClientAssignments(clients);
assertThat(sortedAssignments.get(p1), equalTo(expectedClientOneAssignment));
assertThat(sortedAssignments.get(p2), equalTo(expectedClientTwoAssignment));
assertThat(sortedAssignments.get(p3), equalTo(expectedClientThreeAssignment));
assertThat(sortedAssignments.get(p4), equalTo(expectedClientFourAssignment));
assertThat(sortedAssignments.get(UUID_1), equalTo(expectedClientOneAssignment));
assertThat(sortedAssignments.get(UUID_2), equalTo(expectedClientTwoAssignment));
assertThat(sortedAssignments.get(UUID_3), equalTo(expectedClientThreeAssignment));
assertThat(sortedAssignments.get(UUID_4), equalTo(expectedClientFourAssignment));
}
@Test
public void shouldNotHaveSameAssignmentOnAnyTwoHosts() {
createClient(p1, 1);
createClient(p2, 1);
createClient(p3, 1);
createClient(p4, 1);
final List<UUID> allUUIDs = asList(UUID_1, UUID_2, UUID_3, UUID_4);
createClient(UUID_1, 1);
createClient(UUID_2, 1);
createClient(UUID_3, 1);
createClient(UUID_4, 1);
final StickyTaskAssignor<Integer> taskAssignor = createTaskAssignor(1, task00, task02, task01, task03);
final StickyTaskAssignor taskAssignor = createTaskAssignor(1, TASK_0_0, TASK_0_2, TASK_0_1, TASK_0_3);
taskAssignor.assign();
for (int i = p1; i <= p4; i++) {
final Set<TaskId> taskIds = clients.get(i).assignedTasks();
for (int j = p1; j <= p4; j++) {
if (j != i) {
assertThat("clients shouldn't have same task assignment", clients.get(j).assignedTasks(),
for (final UUID uuid : allUUIDs) {
final Set<TaskId> taskIds = clients.get(uuid).assignedTasks();
for (final UUID otherUUID : allUUIDs) {
if (!uuid.equals(otherUUID)) {
assertThat("clients shouldn't have same task assignment", clients.get(otherUUID).assignedTasks(),
not(equalTo(taskIds)));
}
}
@ -427,19 +429,20 @@ public class StickyTaskAssignorTest {
@Test
public void shouldNotHaveSameAssignmentOnAnyTwoHostsWhenThereArePreviousActiveTasks() {
createClientWithPreviousActiveTasks(p1, 1, task01, task02);
createClientWithPreviousActiveTasks(p2, 1, task03);
createClientWithPreviousActiveTasks(p3, 1, task00);
createClient(p4, 1);
final List<UUID> allUUIDs = asList(UUID_1, UUID_2, UUID_3);
createClientWithPreviousActiveTasks(UUID_1, 1, TASK_0_1, TASK_0_2);
createClientWithPreviousActiveTasks(UUID_2, 1, TASK_0_3);
createClientWithPreviousActiveTasks(UUID_3, 1, TASK_0_0);
createClient(UUID_4, 1);
final StickyTaskAssignor<Integer> taskAssignor = createTaskAssignor(1, task00, task02, task01, task03);
final StickyTaskAssignor taskAssignor = createTaskAssignor(1, TASK_0_0, TASK_0_2, TASK_0_1, TASK_0_3);
taskAssignor.assign();
for (int i = p1; i <= p4; i++) {
final Set<TaskId> taskIds = clients.get(i).assignedTasks();
for (int j = p1; j <= p4; j++) {
if (j != i) {
assertThat("clients shouldn't have same task assignment", clients.get(j).assignedTasks(),
for (final UUID uuid : allUUIDs) {
final Set<TaskId> taskIds = clients.get(uuid).assignedTasks();
for (final UUID otherUUID : allUUIDs) {
if (!uuid.equals(otherUUID)) {
assertThat("clients shouldn't have same task assignment", clients.get(otherUUID).assignedTasks(),
not(equalTo(taskIds)));
}
}
@ -449,22 +452,24 @@ public class StickyTaskAssignorTest {
@Test
public void shouldNotHaveSameAssignmentOnAnyTwoHostsWhenThereArePreviousStandbyTasks() {
final ClientState c1 = createClientWithPreviousActiveTasks(p1, 1, task01, task02);
c1.addPreviousStandbyTasks(Utils.mkSet(task03, task00));
final ClientState c2 = createClientWithPreviousActiveTasks(p2, 1, task03, task00);
c2.addPreviousStandbyTasks(Utils.mkSet(task01, task02));
final List<UUID> allUUIDs = asList(UUID_1, UUID_2, UUID_3, UUID_4);
createClient(p3, 1);
createClient(p4, 1);
final ClientState c1 = createClientWithPreviousActiveTasks(UUID_1, 1, TASK_0_1, TASK_0_2);
c1.addPreviousStandbyTasks(Utils.mkSet(TASK_0_3, TASK_0_0));
final ClientState c2 = createClientWithPreviousActiveTasks(UUID_2, 1, TASK_0_3, TASK_0_0);
c2.addPreviousStandbyTasks(Utils.mkSet(TASK_0_1, TASK_0_2));
final StickyTaskAssignor<Integer> taskAssignor = createTaskAssignor(1, task00, task02, task01, task03);
createClient(UUID_3, 1);
createClient(UUID_4, 1);
final StickyTaskAssignor taskAssignor = createTaskAssignor(1, TASK_0_0, TASK_0_2, TASK_0_1, TASK_0_3);
taskAssignor.assign();
for (int i = p1; i <= p4; i++) {
final Set<TaskId> taskIds = clients.get(i).assignedTasks();
for (int j = p1; j <= p4; j++) {
if (j != i) {
assertThat("clients shouldn't have same task assignment", clients.get(j).assignedTasks(),
for (final UUID uuid : allUUIDs) {
final Set<TaskId> taskIds = clients.get(uuid).assignedTasks();
for (final UUID otherUUID : allUUIDs) {
if (!uuid.equals(otherUUID)) {
assertThat("clients shouldn't have same task assignment", clients.get(otherUUID).assignedTasks(),
not(equalTo(taskIds)));
}
}
@ -474,227 +479,208 @@ public class StickyTaskAssignorTest {
@Test
public void shouldReBalanceTasksAcrossAllClientsWhenCapacityAndTaskCountTheSame() {
createClientWithPreviousActiveTasks(p3, 1, task00, task01, task02, task03);
createClient(p1, 1);
createClient(p2, 1);
createClient(p4, 1);
createClientWithPreviousActiveTasks(UUID_3, 1, TASK_0_0, TASK_0_1, TASK_0_2, TASK_0_3);
createClient(UUID_1, 1);
createClient(UUID_2, 1);
createClient(UUID_4, 1);
final StickyTaskAssignor<Integer> taskAssignor = createTaskAssignor(task00, task02, task01, task03);
final StickyTaskAssignor taskAssignor = createTaskAssignor(TASK_0_0, TASK_0_2, TASK_0_1, TASK_0_3);
taskAssignor.assign();
assertThat(clients.get(p1).assignedTaskCount(), equalTo(1));
assertThat(clients.get(p2).assignedTaskCount(), equalTo(1));
assertThat(clients.get(p3).assignedTaskCount(), equalTo(1));
assertThat(clients.get(p4).assignedTaskCount(), equalTo(1));
assertThat(clients.get(UUID_1).assignedTaskCount(), equalTo(1));
assertThat(clients.get(UUID_2).assignedTaskCount(), equalTo(1));
assertThat(clients.get(UUID_3).assignedTaskCount(), equalTo(1));
assertThat(clients.get(UUID_4).assignedTaskCount(), equalTo(1));
}
@Test
public void shouldReBalanceTasksAcrossClientsWhenCapacityLessThanTaskCount() {
createClientWithPreviousActiveTasks(p3, 1, task00, task01, task02, task03);
createClient(p1, 1);
createClient(p2, 1);
createClientWithPreviousActiveTasks(UUID_3, 1, TASK_0_0, TASK_0_1, TASK_0_2, TASK_0_3);
createClient(UUID_1, 1);
createClient(UUID_2, 1);
final StickyTaskAssignor<Integer> taskAssignor = createTaskAssignor(task00, task02, task01, task03);
final StickyTaskAssignor taskAssignor = createTaskAssignor(TASK_0_0, TASK_0_2, TASK_0_1, TASK_0_3);
taskAssignor.assign();
assertThat(clients.get(p3).assignedTaskCount(), equalTo(2));
assertThat(clients.get(p1).assignedTaskCount(), equalTo(1));
assertThat(clients.get(p2).assignedTaskCount(), equalTo(1));
assertThat(clients.get(UUID_3).assignedTaskCount(), equalTo(2));
assertThat(clients.get(UUID_1).assignedTaskCount(), equalTo(1));
assertThat(clients.get(UUID_2).assignedTaskCount(), equalTo(1));
}
@Test
public void shouldRebalanceTasksToClientsBasedOnCapacity() {
createClientWithPreviousActiveTasks(p2, 1, task00, task03, task02);
createClient(p3, 2);
final StickyTaskAssignor<Integer> taskAssignor = createTaskAssignor(task00, task02, task03);
createClientWithPreviousActiveTasks(UUID_2, 1, TASK_0_0, TASK_0_3, TASK_0_2);
createClient(UUID_3, 2);
final StickyTaskAssignor taskAssignor = createTaskAssignor(TASK_0_0, TASK_0_2, TASK_0_3);
taskAssignor.assign();
assertThat(clients.get(p2).assignedTaskCount(), equalTo(1));
assertThat(clients.get(p3).assignedTaskCount(), equalTo(2));
assertThat(clients.get(UUID_2).assignedTaskCount(), equalTo(1));
assertThat(clients.get(UUID_3).assignedTaskCount(), equalTo(2));
}
@Test
public void shouldMoveMinimalNumberOfTasksWhenPreviouslyAboveCapacityAndNewClientAdded() {
final Set<TaskId> p1PrevTasks = Utils.mkSet(task00, task02);
final Set<TaskId> p2PrevTasks = Utils.mkSet(task01, task03);
final Set<TaskId> p1PrevTasks = Utils.mkSet(TASK_0_0, TASK_0_2);
final Set<TaskId> p2PrevTasks = Utils.mkSet(TASK_0_1, TASK_0_3);
createClientWithPreviousActiveTasks(p1, 1, task00, task02);
createClientWithPreviousActiveTasks(p2, 1, task01, task03);
createClientWithPreviousActiveTasks(p3, 1);
createClientWithPreviousActiveTasks(UUID_1, 1, TASK_0_0, TASK_0_2);
createClientWithPreviousActiveTasks(UUID_2, 1, TASK_0_1, TASK_0_3);
createClientWithPreviousActiveTasks(UUID_3, 1);
final StickyTaskAssignor<Integer> taskAssignor = createTaskAssignor(task00, task02, task01, task03);
final StickyTaskAssignor taskAssignor = createTaskAssignor(TASK_0_0, TASK_0_2, TASK_0_1, TASK_0_3);
taskAssignor.assign();
final Set<TaskId> p3ActiveTasks = clients.get(p3).activeTasks();
final Set<TaskId> p3ActiveTasks = clients.get(UUID_3).activeTasks();
assertThat(p3ActiveTasks.size(), equalTo(1));
if (p1PrevTasks.removeAll(p3ActiveTasks)) {
assertThat(clients.get(p2).activeTasks(), equalTo(p2PrevTasks));
assertThat(clients.get(UUID_2).activeTasks(), equalTo(p2PrevTasks));
} else {
assertThat(clients.get(p1).activeTasks(), equalTo(p1PrevTasks));
assertThat(clients.get(UUID_1).activeTasks(), equalTo(p1PrevTasks));
}
}
@Test
public void shouldNotMoveAnyTasksWhenNewTasksAdded() {
createClientWithPreviousActiveTasks(p1, 1, task00, task01);
createClientWithPreviousActiveTasks(p2, 1, task02, task03);
createClientWithPreviousActiveTasks(UUID_1, 1, TASK_0_0, TASK_0_1);
createClientWithPreviousActiveTasks(UUID_2, 1, TASK_0_2, TASK_0_3);
final StickyTaskAssignor<Integer> taskAssignor = createTaskAssignor(task03, task01, task04, task02, task00, task05);
final StickyTaskAssignor taskAssignor = createTaskAssignor(TASK_0_3, TASK_0_1, TASK_0_4, TASK_0_2, TASK_0_0, TASK_0_5);
taskAssignor.assign();
assertThat(clients.get(p1).activeTasks(), hasItems(task00, task01));
assertThat(clients.get(p2).activeTasks(), hasItems(task02, task03));
assertThat(clients.get(UUID_1).activeTasks(), hasItems(TASK_0_0, TASK_0_1));
assertThat(clients.get(UUID_2).activeTasks(), hasItems(TASK_0_2, TASK_0_3));
}
@Test
public void shouldAssignNewTasksToNewClientWhenPreviousTasksAssignedToOldClients() {
createClientWithPreviousActiveTasks(p1, 1, task02, task01);
createClientWithPreviousActiveTasks(p2, 1, task00, task03);
createClient(p3, 1);
createClientWithPreviousActiveTasks(UUID_1, 1, TASK_0_2, TASK_0_1);
createClientWithPreviousActiveTasks(UUID_2, 1, TASK_0_0, TASK_0_3);
createClient(UUID_3, 1);
final StickyTaskAssignor<Integer> taskAssignor = createTaskAssignor(task03, task01, task04, task02, task00, task05);
final StickyTaskAssignor taskAssignor = createTaskAssignor(TASK_0_3, TASK_0_1, TASK_0_4, TASK_0_2, TASK_0_0, TASK_0_5);
taskAssignor.assign();
assertThat(clients.get(p1).activeTasks(), hasItems(task02, task01));
assertThat(clients.get(p2).activeTasks(), hasItems(task00, task03));
assertThat(clients.get(p3).activeTasks(), hasItems(task04, task05));
assertThat(clients.get(UUID_1).activeTasks(), hasItems(TASK_0_2, TASK_0_1));
assertThat(clients.get(UUID_2).activeTasks(), hasItems(TASK_0_0, TASK_0_3));
assertThat(clients.get(UUID_3).activeTasks(), hasItems(TASK_0_4, TASK_0_5));
}
@Test
public void shouldAssignTasksNotPreviouslyActiveToNewClient() {
final TaskId task10 = new TaskId(0, 10);
final TaskId task11 = new TaskId(0, 11);
final TaskId task12 = new TaskId(1, 2);
final TaskId task13 = new TaskId(1, 3);
final TaskId task20 = new TaskId(2, 0);
final TaskId task21 = new TaskId(2, 1);
final TaskId task22 = new TaskId(2, 2);
final TaskId task23 = new TaskId(2, 3);
final ClientState c1 = createClientWithPreviousActiveTasks(UUID_1, 1, TASK_0_1, TASK_1_2, TASK_1_3);
c1.addPreviousStandbyTasks(Utils.mkSet(TASK_0_0, TASK_1_1, TASK_2_0, TASK_2_1, TASK_2_3));
final ClientState c2 = createClientWithPreviousActiveTasks(UUID_2, 1, TASK_0_0, TASK_1_1, TASK_2_2);
c2.addPreviousStandbyTasks(Utils.mkSet(TASK_0_1, TASK_1_0, TASK_0_2, TASK_2_0, TASK_0_3, TASK_1_2, TASK_2_1, TASK_1_3, TASK_2_3));
final ClientState c3 = createClientWithPreviousActiveTasks(UUID_3, 1, TASK_2_0, TASK_2_1, TASK_2_3);
c3.addPreviousStandbyTasks(Utils.mkSet(TASK_0_2, TASK_1_2));
final ClientState c1 = createClientWithPreviousActiveTasks(p1, 1, task01, task12, task13);
c1.addPreviousStandbyTasks(Utils.mkSet(task00, task11, task20, task21, task23));
final ClientState c2 = createClientWithPreviousActiveTasks(p2, 1, task00, task11, task22);
c2.addPreviousStandbyTasks(Utils.mkSet(task01, task10, task02, task20, task03, task12, task21, task13, task23));
final ClientState c3 = createClientWithPreviousActiveTasks(p3, 1, task20, task21, task23);
c3.addPreviousStandbyTasks(Utils.mkSet(task02, task12));
final ClientState newClient = createClient(UUID_4, 1);
newClient.addPreviousStandbyTasks(Utils.mkSet(TASK_0_0, TASK_1_0, TASK_0_1, TASK_0_2, TASK_1_1, TASK_2_0, TASK_0_3, TASK_1_2, TASK_2_1, TASK_1_3, TASK_2_2, TASK_2_3));
final ClientState newClient = createClient(p4, 1);
newClient.addPreviousStandbyTasks(Utils.mkSet(task00, task10, task01, task02, task11, task20, task03, task12, task21, task13, task22, task23));
final StickyTaskAssignor<Integer> taskAssignor = createTaskAssignor(task00, task10, task01, task02, task11, task20, task03, task12, task21, task13, task22, task23);
final StickyTaskAssignor taskAssignor = createTaskAssignor(TASK_0_0, TASK_1_0, TASK_0_1, TASK_0_2, TASK_1_1, TASK_2_0, TASK_0_3, TASK_1_2, TASK_2_1, TASK_1_3, TASK_2_2, TASK_2_3);
taskAssignor.assign();
assertThat(c1.activeTasks(), equalTo(Utils.mkSet(task01, task12, task13)));
assertThat(c2.activeTasks(), equalTo(Utils.mkSet(task00, task11, task22)));
assertThat(c3.activeTasks(), equalTo(Utils.mkSet(task20, task21, task23)));
assertThat(newClient.activeTasks(), equalTo(Utils.mkSet(task02, task03, task10)));
assertThat(c1.activeTasks(), equalTo(Utils.mkSet(TASK_0_1, TASK_1_2, TASK_1_3)));
assertThat(c2.activeTasks(), equalTo(Utils.mkSet(TASK_0_0, TASK_1_1, TASK_2_2)));
assertThat(c3.activeTasks(), equalTo(Utils.mkSet(TASK_2_0, TASK_2_1, TASK_2_3)));
assertThat(newClient.activeTasks(), equalTo(Utils.mkSet(TASK_0_2, TASK_0_3, TASK_1_0)));
}
@Test
public void shouldAssignTasksNotPreviouslyActiveToMultipleNewClients() {
final TaskId task10 = new TaskId(0, 10);
final TaskId task11 = new TaskId(0, 11);
final TaskId task12 = new TaskId(1, 2);
final TaskId task13 = new TaskId(1, 3);
final TaskId task20 = new TaskId(2, 0);
final TaskId task21 = new TaskId(2, 1);
final TaskId task22 = new TaskId(2, 2);
final TaskId task23 = new TaskId(2, 3);
final ClientState c1 = createClientWithPreviousActiveTasks(UUID_1, 1, TASK_0_1, TASK_1_2, TASK_1_3);
c1.addPreviousStandbyTasks(Utils.mkSet(TASK_0_0, TASK_1_1, TASK_2_0, TASK_2_1, TASK_2_3));
final ClientState c2 = createClientWithPreviousActiveTasks(UUID_2, 1, TASK_0_0, TASK_1_1, TASK_2_2);
c2.addPreviousStandbyTasks(Utils.mkSet(TASK_0_1, TASK_1_0, TASK_0_2, TASK_2_0, TASK_0_3, TASK_1_2, TASK_2_1, TASK_1_3, TASK_2_3));
final ClientState c1 = createClientWithPreviousActiveTasks(p1, 1, task01, task12, task13);
c1.addPreviousStandbyTasks(Utils.mkSet(task00, task11, task20, task21, task23));
final ClientState c2 = createClientWithPreviousActiveTasks(p2, 1, task00, task11, task22);
c2.addPreviousStandbyTasks(Utils.mkSet(task01, task10, task02, task20, task03, task12, task21, task13, task23));
final ClientState bounce1 = createClient(UUID_3, 1);
bounce1.addPreviousStandbyTasks(Utils.mkSet(TASK_2_0, TASK_2_1, TASK_2_3));
final ClientState bounce1 = createClient(p3, 1);
bounce1.addPreviousStandbyTasks(Utils.mkSet(task20, task21, task23));
final ClientState bounce2 = createClient(UUID_4, 1);
bounce2.addPreviousStandbyTasks(Utils.mkSet(TASK_0_2, TASK_0_3, TASK_1_0));
final ClientState bounce2 = createClient(p4, 1);
bounce2.addPreviousStandbyTasks(Utils.mkSet(task02, task03, task10));
final StickyTaskAssignor<Integer> taskAssignor = createTaskAssignor(task00, task10, task01, task02, task11, task20, task03, task12, task21, task13, task22, task23);
final StickyTaskAssignor taskAssignor = createTaskAssignor(TASK_0_0, TASK_1_0, TASK_0_1, TASK_0_2, TASK_1_1, TASK_2_0, TASK_0_3, TASK_1_2, TASK_2_1, TASK_1_3, TASK_2_2, TASK_2_3);
taskAssignor.assign();
assertThat(c1.activeTasks(), equalTo(Utils.mkSet(task01, task12, task13)));
assertThat(c2.activeTasks(), equalTo(Utils.mkSet(task00, task11, task22)));
assertThat(bounce1.activeTasks(), equalTo(Utils.mkSet(task20, task21, task23)));
assertThat(bounce2.activeTasks(), equalTo(Utils.mkSet(task02, task03, task10)));
assertThat(c1.activeTasks(), equalTo(Utils.mkSet(TASK_0_1, TASK_1_2, TASK_1_3)));
assertThat(c2.activeTasks(), equalTo(Utils.mkSet(TASK_0_0, TASK_1_1, TASK_2_2)));
assertThat(bounce1.activeTasks(), equalTo(Utils.mkSet(TASK_2_0, TASK_2_1, TASK_2_3)));
assertThat(bounce2.activeTasks(), equalTo(Utils.mkSet(TASK_0_2, TASK_0_3, TASK_1_0)));
}
@Test
public void shouldAssignTasksToNewClient() {
createClientWithPreviousActiveTasks(p1, 1, task01, task02);
createClient(p2, 1);
createTaskAssignor(task01, task02).assign();
assertThat(clients.get(p1).activeTaskCount(), equalTo(1));
createClientWithPreviousActiveTasks(UUID_1, 1, TASK_0_1, TASK_0_2);
createClient(UUID_2, 1);
createTaskAssignor(TASK_0_1, TASK_0_2).assign();
assertThat(clients.get(UUID_1).activeTaskCount(), equalTo(1));
}
@Test
public void shouldAssignTasksToNewClientWithoutFlippingAssignmentBetweenExistingClients() {
final ClientState c1 = createClientWithPreviousActiveTasks(p1, 1, task00, task01, task02);
final ClientState c2 = createClientWithPreviousActiveTasks(p2, 1, task03, task04, task05);
final ClientState newClient = createClient(p3, 1);
final ClientState c1 = createClientWithPreviousActiveTasks(UUID_1, 1, TASK_0_0, TASK_0_1, TASK_0_2);
final ClientState c2 = createClientWithPreviousActiveTasks(UUID_2, 1, TASK_0_3, TASK_0_4, TASK_0_5);
final ClientState newClient = createClient(UUID_3, 1);
final StickyTaskAssignor<Integer> taskAssignor = createTaskAssignor(task00, task01, task02, task03, task04, task05);
final StickyTaskAssignor taskAssignor = createTaskAssignor(TASK_0_0, TASK_0_1, TASK_0_2, TASK_0_3, TASK_0_4, TASK_0_5);
taskAssignor.assign();
assertThat(c1.activeTasks(), not(hasItem(task03)));
assertThat(c1.activeTasks(), not(hasItem(task04)));
assertThat(c1.activeTasks(), not(hasItem(task05)));
assertThat(c1.activeTasks(), not(hasItem(TASK_0_3)));
assertThat(c1.activeTasks(), not(hasItem(TASK_0_4)));
assertThat(c1.activeTasks(), not(hasItem(TASK_0_5)));
assertThat(c1.activeTaskCount(), equalTo(2));
assertThat(c2.activeTasks(), not(hasItems(task00)));
assertThat(c2.activeTasks(), not(hasItems(task01)));
assertThat(c2.activeTasks(), not(hasItems(task02)));
assertThat(c2.activeTasks(), not(hasItems(TASK_0_0)));
assertThat(c2.activeTasks(), not(hasItems(TASK_0_1)));
assertThat(c2.activeTasks(), not(hasItems(TASK_0_2)));
assertThat(c2.activeTaskCount(), equalTo(2));
assertThat(newClient.activeTaskCount(), equalTo(2));
}
@Test
public void shouldAssignTasksToNewClientWithoutFlippingAssignmentBetweenExistingAndBouncedClients() {
final TaskId task06 = new TaskId(0, 6);
final ClientState c1 = createClientWithPreviousActiveTasks(p1, 1, task00, task01, task02, task06);
final ClientState c2 = createClient(p2, 1);
c2.addPreviousStandbyTasks(Utils.mkSet(task03, task04, task05));
final ClientState newClient = createClient(p3, 1);
final ClientState c1 = createClientWithPreviousActiveTasks(UUID_1, 1, TASK_0_0, TASK_0_1, TASK_0_2, TASK_0_6);
final ClientState c2 = createClient(UUID_2, 1);
c2.addPreviousStandbyTasks(Utils.mkSet(TASK_0_3, TASK_0_4, TASK_0_5));
final ClientState newClient = createClient(UUID_3, 1);
final StickyTaskAssignor<Integer> taskAssignor = createTaskAssignor(task00, task01, task02, task03, task04, task05, task06);
final StickyTaskAssignor taskAssignor = createTaskAssignor(TASK_0_0, TASK_0_1, TASK_0_2, TASK_0_3, TASK_0_4, TASK_0_5, TASK_0_6);
taskAssignor.assign();
assertThat(c1.activeTasks(), not(hasItem(task03)));
assertThat(c1.activeTasks(), not(hasItem(task04)));
assertThat(c1.activeTasks(), not(hasItem(task05)));
assertThat(c1.activeTasks(), not(hasItem(TASK_0_3)));
assertThat(c1.activeTasks(), not(hasItem(TASK_0_4)));
assertThat(c1.activeTasks(), not(hasItem(TASK_0_5)));
assertThat(c1.activeTaskCount(), equalTo(3));
assertThat(c2.activeTasks(), not(hasItems(task00)));
assertThat(c2.activeTasks(), not(hasItems(task01)));
assertThat(c2.activeTasks(), not(hasItems(task02)));
assertThat(c2.activeTasks(), not(hasItems(TASK_0_0)));
assertThat(c2.activeTasks(), not(hasItems(TASK_0_1)));
assertThat(c2.activeTasks(), not(hasItems(TASK_0_2)));
assertThat(c2.activeTaskCount(), equalTo(2));
assertThat(newClient.activeTaskCount(), equalTo(2));
}
@Test
public void shouldViolateBalanceToPreserveActiveTaskStickiness() {
final ClientState c1 = createClientWithPreviousActiveTasks(p1, 1, task00, task01, task02);
final ClientState c2 = createClient(p2, 1);
final ClientState c1 = createClientWithPreviousActiveTasks(UUID_1, 1, TASK_0_0, TASK_0_1, TASK_0_2);
final ClientState c2 = createClient(UUID_2, 1);
final StickyTaskAssignor<Integer> taskAssignor = createTaskAssignor(0, true, task00, task01, task02);
final StickyTaskAssignor taskAssignor = createTaskAssignor(0, true, TASK_0_0, TASK_0_1, TASK_0_2);
taskAssignor.assign();
assertThat(c1.activeTasks(), equalTo(Utils.mkSet(task00, task01, task02)));
assertThat(c1.activeTasks(), equalTo(Utils.mkSet(TASK_0_0, TASK_0_1, TASK_0_2)));
assertTrue(c2.activeTasks().isEmpty());
}
private StickyTaskAssignor<Integer> createTaskAssignor(final TaskId... tasks) {
private StickyTaskAssignor createTaskAssignor(final TaskId... tasks) {
return createTaskAssignor(0, false, tasks);
}
private StickyTaskAssignor<Integer> createTaskAssignor(final int numStandbys, final TaskId... tasks) {
private StickyTaskAssignor createTaskAssignor(final int numStandbys, final TaskId... tasks) {
return createTaskAssignor(numStandbys, false, tasks);
}
private StickyTaskAssignor<Integer> createTaskAssignor(final int numStandbys,
private StickyTaskAssignor createTaskAssignor(final int numStandbys,
final boolean mustPreserveActiveTaskAssignment,
final TaskId... tasks) {
final List<TaskId> taskIds = Arrays.asList(tasks);
final List<TaskId> taskIds = asList(tasks);
Collections.shuffle(taskIds);
return new StickyTaskAssignor<>(
return new StickyTaskAssignor(
clients,
new HashSet<>(taskIds),
new HashSet<>(taskIds),
@ -721,11 +707,11 @@ public class StickyTaskAssignorTest {
return tasks;
}
private ClientState createClient(final Integer processId, final int capacity) {
private ClientState createClient(final UUID processId, final int capacity) {
return createClientWithPreviousActiveTasks(processId, capacity);
}
private ClientState createClientWithPreviousActiveTasks(final Integer processId, final int capacity, final TaskId... taskIds) {
private ClientState createClientWithPreviousActiveTasks(final UUID processId, final int capacity, final TaskId... taskIds) {
final ClientState clientState = new ClientState(capacity);
clientState.addPreviousActiveTasks(Utils.mkSet(taskIds));
clients.put(processId, clientState);
@ -733,7 +719,7 @@ public class StickyTaskAssignorTest {
}
private void assertActiveTaskTopicGroupIdsEvenlyDistributed() {
for (final Map.Entry<Integer, ClientState> clientStateEntry : clients.entrySet()) {
for (final Map.Entry<UUID, ClientState> clientStateEntry : clients.entrySet()) {
final List<Integer> topicGroupIds = new ArrayList<>();
final Set<TaskId> activeTasks = clientStateEntry.getValue().activeTasks();
for (final TaskId activeTask : activeTasks) {
@ -744,9 +730,9 @@ public class StickyTaskAssignorTest {
}
}
private Map<Integer, Set<TaskId>> sortClientAssignments(final Map<Integer, ClientState> clients) {
final Map<Integer, Set<TaskId>> sortedAssignments = new HashMap<>();
for (final Map.Entry<Integer, ClientState> entry : clients.entrySet()) {
private Map<UUID, Set<TaskId>> sortClientAssignments(final Map<UUID, ClientState> clients) {
final Map<UUID, Set<TaskId>> sortedAssignments = new HashMap<>();
for (final Map.Entry<UUID, ClientState> entry : clients.entrySet()) {
final Set<TaskId> sorted = new TreeSet<>(entry.getValue().activeTasks());
sortedAssignments.put(entry.getKey(), sorted);
}

View File

@ -25,10 +25,15 @@ import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import static org.apache.kafka.common.utils.Utils.mkEntry;
import static org.apache.kafka.common.utils.Utils.mkMap;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_0_0;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_0_1;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_1_0;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_1_1;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_2_0;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.UUID_1;
import static org.apache.kafka.streams.processor.internals.assignment.StreamsAssignmentProtocolVersions.LATEST_SUPPORTED_VERSION;
import static org.apache.kafka.streams.processor.internals.assignment.SubscriptionInfo.MIN_VERSION_OFFSET_SUM_SUBSCRIPTION;
import static org.apache.kafka.streams.processor.internals.assignment.SubscriptionInfo.UNKNOWN_OFFSET_SUM;
@ -38,20 +43,19 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
public class SubscriptionInfoTest {
private final UUID processId = UUID.randomUUID();
private static final Set<TaskId> ACTIVE_TASKS = new HashSet<>(Arrays.asList(
new TaskId(0, 0),
new TaskId(0, 1),
new TaskId(1, 0)));
TASK_0_0,
TASK_0_1,
TASK_1_0));
private static final Set<TaskId> STANDBY_TASKS = new HashSet<>(Arrays.asList(
new TaskId(1, 1),
new TaskId(2, 0)));
TASK_1_1,
TASK_2_0));
private static final Map<TaskId, Long> TASK_OFFSET_SUMS = mkMap(
mkEntry(new TaskId(0, 0), Task.LATEST_OFFSET),
mkEntry(new TaskId(0, 1), Task.LATEST_OFFSET),
mkEntry(new TaskId(1, 0), Task.LATEST_OFFSET),
mkEntry(new TaskId(1, 1), 0L),
mkEntry(new TaskId(2, 0), 10L)
mkEntry(TASK_0_0, Task.LATEST_OFFSET),
mkEntry(TASK_0_1, Task.LATEST_OFFSET),
mkEntry(TASK_1_0, Task.LATEST_OFFSET),
mkEntry(TASK_1_1, 0L),
mkEntry(TASK_2_0, 10L)
);
private final static String IGNORED_USER_ENDPOINT = "ignoredUserEndpoint:80";
@ -61,7 +65,7 @@ public class SubscriptionInfoTest {
new SubscriptionInfo(
0,
LATEST_SUPPORTED_VERSION,
processId,
UUID_1,
"localhost:80",
TASK_OFFSET_SUMS
);
@ -72,7 +76,7 @@ public class SubscriptionInfoTest {
new SubscriptionInfo(
LATEST_SUPPORTED_VERSION + 1,
LATEST_SUPPORTED_VERSION,
processId,
UUID_1,
"localhost:80",
TASK_OFFSET_SUMS
);
@ -83,14 +87,14 @@ public class SubscriptionInfoTest {
final SubscriptionInfo info = new SubscriptionInfo(
1,
LATEST_SUPPORTED_VERSION,
processId,
UUID_1,
IGNORED_USER_ENDPOINT,
TASK_OFFSET_SUMS
);
final SubscriptionInfo decoded = SubscriptionInfo.decode(info.encode());
assertEquals(1, decoded.version());
assertEquals(SubscriptionInfo.UNKNOWN, decoded.latestSupportedVersion());
assertEquals(processId, decoded.processId());
assertEquals(UUID_1, decoded.processId());
assertEquals(ACTIVE_TASKS, decoded.prevTasks());
assertEquals(STANDBY_TASKS, decoded.standbyTasks());
assertNull(decoded.userEndPoint());
@ -101,7 +105,7 @@ public class SubscriptionInfoTest {
final SubscriptionInfo info = new SubscriptionInfo(
1,
1234,
processId,
UUID_1,
"ignoreme",
TASK_OFFSET_SUMS
);
@ -110,7 +114,7 @@ public class SubscriptionInfoTest {
final LegacySubscriptionInfoSerde decoded = LegacySubscriptionInfoSerde.decode(buffer);
assertEquals(1, decoded.version());
assertEquals(SubscriptionInfo.UNKNOWN, decoded.latestSupportedVersion());
assertEquals(processId, decoded.processId());
assertEquals(UUID_1, decoded.processId());
assertEquals(ACTIVE_TASKS, decoded.prevTasks());
assertEquals(STANDBY_TASKS, decoded.standbyTasks());
assertNull(decoded.userEndPoint());
@ -121,7 +125,7 @@ public class SubscriptionInfoTest {
final LegacySubscriptionInfoSerde info = new LegacySubscriptionInfoSerde(
1,
LATEST_SUPPORTED_VERSION,
processId,
UUID_1,
ACTIVE_TASKS,
STANDBY_TASKS,
"localhost:80"
@ -131,7 +135,7 @@ public class SubscriptionInfoTest {
final SubscriptionInfo decoded = SubscriptionInfo.decode(buffer);
assertEquals(1, decoded.version());
assertEquals(SubscriptionInfo.UNKNOWN, decoded.latestSupportedVersion());
assertEquals(processId, decoded.processId());
assertEquals(UUID_1, decoded.processId());
assertEquals(ACTIVE_TASKS, decoded.prevTasks());
assertEquals(STANDBY_TASKS, decoded.standbyTasks());
assertNull(decoded.userEndPoint());
@ -142,14 +146,14 @@ public class SubscriptionInfoTest {
final SubscriptionInfo info = new SubscriptionInfo(
2,
LATEST_SUPPORTED_VERSION,
processId,
UUID_1,
"localhost:80",
TASK_OFFSET_SUMS
);
final SubscriptionInfo decoded = SubscriptionInfo.decode(info.encode());
assertEquals(2, decoded.version());
assertEquals(SubscriptionInfo.UNKNOWN, decoded.latestSupportedVersion());
assertEquals(processId, decoded.processId());
assertEquals(UUID_1, decoded.processId());
assertEquals(ACTIVE_TASKS, decoded.prevTasks());
assertEquals(STANDBY_TASKS, decoded.standbyTasks());
assertEquals("localhost:80", decoded.userEndPoint());
@ -160,7 +164,7 @@ public class SubscriptionInfoTest {
final SubscriptionInfo info = new SubscriptionInfo(
2,
LATEST_SUPPORTED_VERSION,
processId,
UUID_1,
"localhost:80",
TASK_OFFSET_SUMS
);
@ -169,7 +173,7 @@ public class SubscriptionInfoTest {
final LegacySubscriptionInfoSerde decoded = LegacySubscriptionInfoSerde.decode(buffer);
assertEquals(2, decoded.version());
assertEquals(SubscriptionInfo.UNKNOWN, decoded.latestSupportedVersion());
assertEquals(processId, decoded.processId());
assertEquals(UUID_1, decoded.processId());
assertEquals(ACTIVE_TASKS, decoded.prevTasks());
assertEquals(STANDBY_TASKS, decoded.standbyTasks());
assertEquals("localhost:80", decoded.userEndPoint());
@ -180,7 +184,7 @@ public class SubscriptionInfoTest {
final LegacySubscriptionInfoSerde info = new LegacySubscriptionInfoSerde(
2,
LATEST_SUPPORTED_VERSION,
processId,
UUID_1,
ACTIVE_TASKS,
STANDBY_TASKS,
"localhost:80"
@ -190,7 +194,7 @@ public class SubscriptionInfoTest {
final SubscriptionInfo decoded = SubscriptionInfo.decode(buffer);
assertEquals(2, decoded.version());
assertEquals(SubscriptionInfo.UNKNOWN, decoded.latestSupportedVersion());
assertEquals(processId, decoded.processId());
assertEquals(UUID_1, decoded.processId());
assertEquals(ACTIVE_TASKS, decoded.prevTasks());
assertEquals(STANDBY_TASKS, decoded.standbyTasks());
assertEquals("localhost:80", decoded.userEndPoint());
@ -202,14 +206,14 @@ public class SubscriptionInfoTest {
final SubscriptionInfo info = new SubscriptionInfo(
version,
LATEST_SUPPORTED_VERSION,
processId,
UUID_1,
"localhost:80",
TASK_OFFSET_SUMS
);
final SubscriptionInfo decoded = SubscriptionInfo.decode(info.encode());
assertEquals(version, decoded.version());
assertEquals(LATEST_SUPPORTED_VERSION, decoded.latestSupportedVersion());
assertEquals(processId, decoded.processId());
assertEquals(UUID_1, decoded.processId());
assertEquals(ACTIVE_TASKS, decoded.prevTasks());
assertEquals(STANDBY_TASKS, decoded.standbyTasks());
assertEquals("localhost:80", decoded.userEndPoint());
@ -222,7 +226,7 @@ public class SubscriptionInfoTest {
final SubscriptionInfo info = new SubscriptionInfo(
version,
LATEST_SUPPORTED_VERSION,
processId,
UUID_1,
"localhost:80",
TASK_OFFSET_SUMS
);
@ -231,7 +235,7 @@ public class SubscriptionInfoTest {
final LegacySubscriptionInfoSerde decoded = LegacySubscriptionInfoSerde.decode(buffer);
assertEquals(version, decoded.version());
assertEquals(LATEST_SUPPORTED_VERSION, decoded.latestSupportedVersion());
assertEquals(processId, decoded.processId());
assertEquals(UUID_1, decoded.processId());
assertEquals(ACTIVE_TASKS, decoded.prevTasks());
assertEquals(STANDBY_TASKS, decoded.standbyTasks());
assertEquals("localhost:80", decoded.userEndPoint());
@ -244,7 +248,7 @@ public class SubscriptionInfoTest {
final LegacySubscriptionInfoSerde info = new LegacySubscriptionInfoSerde(
version,
LATEST_SUPPORTED_VERSION,
processId,
UUID_1,
ACTIVE_TASKS,
STANDBY_TASKS,
"localhost:80"
@ -255,7 +259,7 @@ public class SubscriptionInfoTest {
final String message = "for version: " + version;
assertEquals(message, version, decoded.version());
assertEquals(message, LATEST_SUPPORTED_VERSION, decoded.latestSupportedVersion());
assertEquals(message, processId, decoded.processId());
assertEquals(message, UUID_1, decoded.processId());
assertEquals(message, ACTIVE_TASKS, decoded.prevTasks());
assertEquals(message, STANDBY_TASKS, decoded.standbyTasks());
assertEquals(message, "localhost:80", decoded.userEndPoint());
@ -265,7 +269,7 @@ public class SubscriptionInfoTest {
@Test
public void shouldEncodeAndDecodeVersion5() {
final SubscriptionInfo info =
new SubscriptionInfo(5, LATEST_SUPPORTED_VERSION, processId, "localhost:80", TASK_OFFSET_SUMS);
new SubscriptionInfo(5, LATEST_SUPPORTED_VERSION, UUID_1, "localhost:80", TASK_OFFSET_SUMS);
assertEquals(info, SubscriptionInfo.decode(info.encode()));
}
@ -282,23 +286,23 @@ public class SubscriptionInfoTest {
final int latestSupportedVersion = LATEST_SUPPORTED_VERSION - 1;
final SubscriptionInfo info =
new SubscriptionInfo(usedVersion, latestSupportedVersion, processId, "localhost:80", TASK_OFFSET_SUMS);
new SubscriptionInfo(usedVersion, latestSupportedVersion, UUID_1, "localhost:80", TASK_OFFSET_SUMS);
final SubscriptionInfo expectedInfo =
new SubscriptionInfo(usedVersion, latestSupportedVersion, processId, "localhost:80", TASK_OFFSET_SUMS);
new SubscriptionInfo(usedVersion, latestSupportedVersion, UUID_1, "localhost:80", TASK_OFFSET_SUMS);
assertEquals(expectedInfo, SubscriptionInfo.decode(info.encode()));
}
@Test
public void shouldEncodeAndDecodeVersion7() {
final SubscriptionInfo info =
new SubscriptionInfo(7, LATEST_SUPPORTED_VERSION, processId, "localhost:80", TASK_OFFSET_SUMS);
new SubscriptionInfo(7, LATEST_SUPPORTED_VERSION, UUID_1, "localhost:80", TASK_OFFSET_SUMS);
assertThat(info, is(SubscriptionInfo.decode(info.encode())));
}
@Test
public void shouldConvertTaskOffsetSumMapToTaskSets() {
final SubscriptionInfo info =
new SubscriptionInfo(7, LATEST_SUPPORTED_VERSION, processId, "localhost:80", TASK_OFFSET_SUMS);
new SubscriptionInfo(7, LATEST_SUPPORTED_VERSION, UUID_1, "localhost:80", TASK_OFFSET_SUMS);
assertThat(info.prevTasks(), is(ACTIVE_TASKS));
assertThat(info.standbyTasks(), is(STANDBY_TASKS));
}
@ -307,7 +311,7 @@ public class SubscriptionInfoTest {
public void shouldReturnTaskOffsetSumsMapForDecodedSubscription() {
final SubscriptionInfo info = SubscriptionInfo.decode(
new SubscriptionInfo(MIN_VERSION_OFFSET_SUM_SUBSCRIPTION,
LATEST_SUPPORTED_VERSION, processId,
LATEST_SUPPORTED_VERSION, UUID_1,
"localhost:80",
TASK_OFFSET_SUMS)
.encode());
@ -328,7 +332,7 @@ public class SubscriptionInfoTest {
new LegacySubscriptionInfoSerde(
SubscriptionInfo.MIN_VERSION_OFFSET_SUM_SUBSCRIPTION - 1,
LATEST_SUPPORTED_VERSION,
processId,
UUID_1,
ACTIVE_TASKS,
STANDBY_TASKS,
"localhost:80")

View File

@ -0,0 +1,129 @@
/*
* 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.kafka.streams.processor.internals.assignment;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static org.apache.kafka.common.utils.Utils.mkEntry;
import static org.apache.kafka.common.utils.Utils.mkMap;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_0_0;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_0_1;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_0_2;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_1_0;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_1_1;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.TASK_1_2;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.UUID_1;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.UUID_2;
import static org.apache.kafka.streams.processor.internals.assignment.AssignmentTestUtils.UUID_3;
import static org.apache.kafka.streams.processor.internals.assignment.TaskMovement.getMovements;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.UUID;
import org.apache.kafka.streams.processor.TaskId;
import org.junit.Test;
public class TaskMovementTest {
@Test
public void shouldGetMovementsFromStateConstrainedToBalancedAssignment() {
final int maxWarmupReplicas = Integer.MAX_VALUE;
final Map<UUID, List<TaskId>> stateConstrainedAssignment = mkMap(
mkEntry(UUID_1, asList(TASK_0_0, TASK_1_2)),
mkEntry(UUID_2, asList(TASK_0_1, TASK_1_0)),
mkEntry(UUID_3, asList(TASK_0_2, TASK_1_1))
);
final Map<UUID, List<TaskId>> balancedAssignment = mkMap(
mkEntry(UUID_1, asList(TASK_0_0, TASK_1_0)),
mkEntry(UUID_2, asList(TASK_0_1, TASK_1_1)),
mkEntry(UUID_3, asList(TASK_0_2, TASK_1_2))
);
final Queue<TaskMovement> expectedMovements = new LinkedList<>();
expectedMovements.add(new TaskMovement(TASK_1_2, UUID_1, UUID_3));
expectedMovements.add(new TaskMovement(TASK_1_0, UUID_2, UUID_1));
expectedMovements.add(new TaskMovement(TASK_1_1, UUID_3, UUID_2));
assertThat(getMovements(stateConstrainedAssignment, balancedAssignment, maxWarmupReplicas), equalTo(expectedMovements));
}
@Test
public void shouldOnlyGetUpToMaxWarmupReplicaMovements() {
final int maxWarmupReplicas = 1;
final Map<UUID, List<TaskId>> stateConstrainedAssignment = mkMap(
mkEntry(UUID_1, asList(TASK_0_0, TASK_1_2)),
mkEntry(UUID_2, asList(TASK_0_1, TASK_1_0)),
mkEntry(UUID_3, asList(TASK_0_2, TASK_1_1))
);
final Map<UUID, List<TaskId>> balancedAssignment = mkMap(
mkEntry(UUID_1, asList(TASK_0_0, TASK_1_0)),
mkEntry(UUID_2, asList(TASK_0_1, TASK_1_1)),
mkEntry(UUID_3, asList(TASK_0_2, TASK_1_2))
);
final Queue<TaskMovement> expectedMovements = new LinkedList<>();
expectedMovements.add(new TaskMovement(TASK_1_2, UUID_1, UUID_3));
assertThat(getMovements(stateConstrainedAssignment, balancedAssignment, maxWarmupReplicas), equalTo(expectedMovements));
}
@Test
public void shouldReturnEmptyMovementsWhenPassedEmptyTaskAssignments() {
final int maxWarmupReplicas = 2;
final Map<UUID, List<TaskId>> stateConstrainedAssignment = mkMap(
mkEntry(UUID_1, emptyList()),
mkEntry(UUID_2, emptyList())
);
final Map<UUID, List<TaskId>> balancedAssignment = mkMap(
mkEntry(UUID_1, emptyList()),
mkEntry(UUID_2, emptyList())
);
assertTrue(getMovements(stateConstrainedAssignment, balancedAssignment, maxWarmupReplicas).isEmpty());
}
@Test
public void shouldReturnEmptyMovementsWhenPassedIdenticalTaskAssignments() {
final int maxWarmupReplicas = 2;
final Map<UUID, List<TaskId>> stateConstrainedAssignment = mkMap(
mkEntry(UUID_1, asList(TASK_0_0, TASK_1_0)),
mkEntry(UUID_2, asList(TASK_0_1, TASK_1_1))
);
final Map<UUID, List<TaskId>> balancedAssignment = mkMap(
mkEntry(UUID_1, asList(TASK_0_0, TASK_1_0)),
mkEntry(UUID_2, asList(TASK_0_1, TASK_1_1))
);
assertTrue(getMovements(stateConstrainedAssignment, balancedAssignment, maxWarmupReplicas).isEmpty());
}
@Test
public void shouldThrowIllegalStateExceptionIfAssignmentsAreOfDifferentSize() {
final int maxWarmupReplicas = 2;
final Map<UUID, List<TaskId>> stateConstrainedAssignment = mkMap(
mkEntry(UUID_1, asList(TASK_0_0, TASK_0_1))
);
final Map<UUID, List<TaskId>> balancedAssignment = mkMap(
mkEntry(UUID_1, asList(TASK_0_0, TASK_1_0)),
mkEntry(UUID_2, asList(TASK_0_1, TASK_1_1))
);
assertThrows(IllegalStateException.class, () -> getMovements(stateConstrainedAssignment, balancedAssignment, maxWarmupReplicas));
}
}