Browse Source
Validate that the assignment is always balanced wrt: * active assignment balance * stateful assignment balance * task-parallel balance Reviewers: Bruno Cadonna <bruno@confluent.io>, A. Sophie Blee-Goldman <sophie@confluent.io>pull/8667/head
John Roesler
5 years ago
committed by
GitHub
23 changed files with 1427 additions and 1583 deletions
@ -1,30 +0,0 @@
@@ -1,30 +0,0 @@
|
||||
/* |
||||
* 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.UUID; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.SortedSet; |
||||
import org.apache.kafka.streams.processor.TaskId; |
||||
|
||||
public interface BalancedAssignor { |
||||
|
||||
Map<UUID, List<TaskId>> assign(final SortedSet<UUID> clients, |
||||
final SortedSet<TaskId> tasks, |
||||
final Map<UUID, Integer> clientsToNumberOfStreamThreads); |
||||
} |
@ -1,86 +0,0 @@
@@ -1,86 +0,0 @@
|
||||
/* |
||||
* 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.UUID; |
||||
import java.util.ArrayList; |
||||
import java.util.HashMap; |
||||
import java.util.Iterator; |
||||
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 implements BalancedAssignor { |
||||
|
||||
@Override |
||||
public Map<UUID, List<TaskId>> assign(final SortedSet<UUID> clients, |
||||
final SortedSet<TaskId> tasks, |
||||
final Map<UUID, Integer> clientsToNumberOfStreamThreads) { |
||||
final Map<UUID, List<TaskId>> assignment = new HashMap<>(); |
||||
clients.forEach(client -> assignment.put(client, new ArrayList<>())); |
||||
distributeTasksEvenlyOverClients(assignment, clients, tasks); |
||||
balanceTasksOverStreamThreads(assignment, clients, clientsToNumberOfStreamThreads); |
||||
return assignment; |
||||
} |
||||
|
||||
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 UUID client : clients) { |
||||
final TaskId task = tasksToAssign.poll(); |
||||
|
||||
if (task == null) { |
||||
break; |
||||
} |
||||
assignment.get(client).add(task); |
||||
} |
||||
} |
||||
} |
||||
|
||||
private void balanceTasksOverStreamThreads(final Map<UUID, List<TaskId>> assignment, |
||||
final SortedSet<UUID> clients, |
||||
final Map<UUID, Integer> clientsToNumberOfStreamThreads) { |
||||
boolean stop = false; |
||||
while (!stop) { |
||||
stop = true; |
||||
for (final UUID sourceClient : clients) { |
||||
final List<TaskId> sourceTasks = assignment.get(sourceClient); |
||||
for (final UUID destinationClient : clients) { |
||||
if (sourceClient.equals(destinationClient)) { |
||||
continue; |
||||
} |
||||
final List<TaskId> destinationTasks = assignment.get(destinationClient); |
||||
final int assignedTasksPerStreamThreadAtDestination = |
||||
destinationTasks.size() / clientsToNumberOfStreamThreads.get(destinationClient); |
||||
final int assignedTasksPerStreamThreadAtSource = |
||||
sourceTasks.size() / clientsToNumberOfStreamThreads.get(sourceClient); |
||||
if (assignedTasksPerStreamThreadAtSource - assignedTasksPerStreamThreadAtDestination > 1) { |
||||
final Iterator<TaskId> sourceIterator = sourceTasks.iterator(); |
||||
final TaskId taskToMove = sourceIterator.next(); |
||||
sourceIterator.remove(); |
||||
destinationTasks.add(taskToMove); |
||||
stop = false; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -1,143 +0,0 @@
@@ -1,143 +0,0 @@
|
||||
/* |
||||
* 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.HashMap; |
||||
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); |
||||
} |
||||
|
||||
/** |
||||
* Maps tasks to clients with caught-up states for the task. |
||||
* |
||||
* @param statefulTasksToRankedClients ranked clients map |
||||
* @return map from tasks with caught-up clients to the list of client candidates |
||||
*/ |
||||
static Map<TaskId, SortedSet<UUID>> tasksToCaughtUpClients(final SortedMap<TaskId, SortedSet<RankedClient>> statefulTasksToRankedClients) { |
||||
final Map<TaskId, SortedSet<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 TreeSet<>()).add(rankedClient.clientId()); |
||||
} else { |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
return taskToCaughtUpClients; |
||||
} |
||||
|
||||
/** |
||||
* 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; |
||||
} |
||||
} |
@ -0,0 +1,111 @@
@@ -0,0 +1,111 @@
|
||||
/* |
||||
* 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 org.apache.kafka.streams.processor.TaskId; |
||||
import org.junit.Test; |
||||
|
||||
import java.util.UUID; |
||||
import java.util.function.BiFunction; |
||||
|
||||
import static java.util.Arrays.asList; |
||||
import static java.util.Collections.singleton; |
||||
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.CoreMatchers.equalTo; |
||||
import static org.hamcrest.CoreMatchers.nullValue; |
||||
import static org.hamcrest.MatcherAssert.assertThat; |
||||
|
||||
public class ConstrainedPrioritySetTest { |
||||
private static final TaskId DUMMY_TASK = new TaskId(0, 0); |
||||
|
||||
private final BiFunction<UUID, TaskId, Boolean> alwaysTrue = (client, task) -> true; |
||||
private final BiFunction<UUID, TaskId, Boolean> alwaysFalse = (client, task) -> false; |
||||
|
||||
@Test |
||||
public void shouldReturnOnlyClient() { |
||||
final ConstrainedPrioritySet queue = new ConstrainedPrioritySet(alwaysTrue, client -> 1.0); |
||||
queue.offerAll(singleton(UUID_1)); |
||||
|
||||
assertThat(queue.poll(DUMMY_TASK), equalTo(UUID_1)); |
||||
assertThat(queue.poll(DUMMY_TASK), nullValue()); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldReturnNull() { |
||||
final ConstrainedPrioritySet queue = new ConstrainedPrioritySet(alwaysFalse, client -> 1.0); |
||||
queue.offerAll(singleton(UUID_1)); |
||||
|
||||
assertThat(queue.poll(DUMMY_TASK), nullValue()); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldReturnLeastLoadedClient() { |
||||
final ConstrainedPrioritySet queue = new ConstrainedPrioritySet( |
||||
alwaysTrue, |
||||
client -> (client == UUID_1) ? 3.0 : (client == UUID_2) ? 2.0 : 1.0 |
||||
); |
||||
|
||||
queue.offerAll(asList(UUID_1, UUID_2, UUID_3)); |
||||
|
||||
assertThat(queue.poll(DUMMY_TASK), equalTo(UUID_3)); |
||||
assertThat(queue.poll(DUMMY_TASK), equalTo(UUID_2)); |
||||
assertThat(queue.poll(DUMMY_TASK), equalTo(UUID_1)); |
||||
assertThat(queue.poll(DUMMY_TASK), nullValue()); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldNotRetainDuplicates() { |
||||
final ConstrainedPrioritySet queue = new ConstrainedPrioritySet(alwaysTrue, client -> 1.0); |
||||
|
||||
queue.offerAll(singleton(UUID_1)); |
||||
queue.offer(UUID_1); |
||||
|
||||
assertThat(queue.poll(DUMMY_TASK), equalTo(UUID_1)); |
||||
assertThat(queue.poll(DUMMY_TASK), nullValue()); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldOnlyReturnValidClients() { |
||||
final ConstrainedPrioritySet queue = new ConstrainedPrioritySet( |
||||
(client, task) -> client.equals(UUID_1), |
||||
client -> 1.0 |
||||
); |
||||
|
||||
queue.offerAll(asList(UUID_1, UUID_2)); |
||||
|
||||
assertThat(queue.poll(DUMMY_TASK), equalTo(UUID_1)); |
||||
assertThat(queue.poll(DUMMY_TASK), nullValue()); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldApplyPollFilter() { |
||||
final ConstrainedPrioritySet queue = new ConstrainedPrioritySet( |
||||
alwaysTrue, |
||||
client -> 1.0 |
||||
); |
||||
|
||||
queue.offerAll(asList(UUID_1, UUID_2)); |
||||
|
||||
assertThat(queue.poll(DUMMY_TASK, client -> client.equals(UUID_1)), equalTo(UUID_1)); |
||||
assertThat(queue.poll(DUMMY_TASK, client -> client.equals(UUID_1)), nullValue()); |
||||
assertThat(queue.poll(DUMMY_TASK), equalTo(UUID_2)); |
||||
assertThat(queue.poll(DUMMY_TASK), nullValue()); |
||||
} |
||||
} |
@ -1,295 +0,0 @@
@@ -1,295 +0,0 @@
|
||||
/* |
||||
* 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.UUID; |
||||
import org.apache.kafka.streams.processor.TaskId; |
||||
import org.junit.Test; |
||||
|
||||
import java.util.Arrays; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.SortedSet; |
||||
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 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 Map<UUID, List<TaskId>> assignment = new DefaultBalancedAssignor().assign( |
||||
THREE_CLIENTS, |
||||
mkSortedSet( |
||||
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) |
||||
); |
||||
|
||||
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)) |
||||
); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldAssignTasksEvenlyOverClientsWhereNumberOfClientsNotIntegralDivisorOfNumberOfTasks() { |
||||
final Map<UUID, List<TaskId>> assignment = new DefaultBalancedAssignor().assign( |
||||
TWO_CLIENTS, |
||||
mkSortedSet( |
||||
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) |
||||
); |
||||
|
||||
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)) |
||||
); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldAssignTasksEvenlyOverClientsWhereNumberOfStreamThreadsIntegralDivisorOfNumberOfTasks() { |
||||
final Map<UUID, List<TaskId>> assignment = new DefaultBalancedAssignor().assign( |
||||
THREE_CLIENTS, |
||||
mkSortedSet( |
||||
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) |
||||
); |
||||
|
||||
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)) |
||||
); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldAssignTasksEvenlyOverClientsWhereNumberOfStreamThreadsNotIntegralDivisorOfNumberOfTasks() { |
||||
final Map<UUID, List<TaskId>> assignment = new DefaultBalancedAssignor().assign( |
||||
THREE_CLIENTS, |
||||
mkSortedSet( |
||||
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) |
||||
); |
||||
|
||||
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)) |
||||
); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldAssignTasksEvenlyOverUnevenlyDistributedStreamThreads() { |
||||
final Map<UUID, List<TaskId>> assignment = new DefaultBalancedAssignor().assign( |
||||
THREE_CLIENTS, |
||||
mkSortedSet( |
||||
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) |
||||
); |
||||
|
||||
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)) |
||||
); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldAssignTasksEvenlyOverClientsWithLessClientsThanTasks() { |
||||
final Map<UUID, List<TaskId>> assignment = new DefaultBalancedAssignor().assign( |
||||
THREE_CLIENTS, |
||||
mkSortedSet( |
||||
TASK_0_0, |
||||
TASK_0_1 |
||||
), |
||||
threeClientsToNumberOfStreamThreads(1, 1, 1) |
||||
); |
||||
|
||||
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, |
||||
is(expectedAssignmentForThreeClients(assignedTasksForClient1, assignedTasksForClient2, assignedTasksForClient3)) |
||||
); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldAssignTasksEvenlyOverClientsAndStreamThreadsWithMoreStreamThreadsThanTasks() { |
||||
final Map<UUID, List<TaskId>> assignment = new DefaultBalancedAssignor().assign( |
||||
THREE_CLIENTS, |
||||
mkSortedSet( |
||||
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) |
||||
); |
||||
|
||||
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)) |
||||
); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldAssignTasksEvenlyOverStreamThreadsButBestEffortOverClients() { |
||||
final Map<UUID, List<TaskId>> assignment = new DefaultBalancedAssignor().assign( |
||||
TWO_CLIENTS, |
||||
mkSortedSet( |
||||
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) |
||||
); |
||||
|
||||
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)) |
||||
); |
||||
} |
||||
|
||||
private static Map<UUID, Integer> twoClientsToNumberOfStreamThreads(final int numberOfStreamThread1, |
||||
final int numberOfStreamThread2) { |
||||
return mkMap( |
||||
mkEntry(UUID_1, numberOfStreamThread1), |
||||
mkEntry(UUID_2, numberOfStreamThread2) |
||||
); |
||||
} |
||||
|
||||
private static Map<UUID, Integer> threeClientsToNumberOfStreamThreads(final int numberOfStreamThread1, |
||||
final int numberOfStreamThread2, |
||||
final int numberOfStreamThread3) { |
||||
return mkMap( |
||||
mkEntry(UUID_1, numberOfStreamThread1), |
||||
mkEntry(UUID_2, numberOfStreamThread2), |
||||
mkEntry(UUID_3, numberOfStreamThread3) |
||||
); |
||||
} |
||||
|
||||
private static Map<UUID, List<TaskId>> expectedAssignmentForThreeClients(final List<TaskId> assignedTasksForClient1, |
||||
final List<TaskId> assignedTasksForClient2, |
||||
final List<TaskId> assignedTasksForClient3) { |
||||
return mkMap( |
||||
mkEntry(UUID_1, assignedTasksForClient1), |
||||
mkEntry(UUID_2, assignedTasksForClient2), |
||||
mkEntry(UUID_3, assignedTasksForClient3) |
||||
); |
||||
} |
||||
|
||||
private static Map<UUID, List<TaskId>> expectedAssignmentForTwoClients(final List<TaskId> assignedTasksForClient1, |
||||
final List<TaskId> assignedTasksForClient2) { |
||||
return mkMap( |
||||
mkEntry(UUID_1, assignedTasksForClient1), |
||||
mkEntry(UUID_2, assignedTasksForClient2) |
||||
); |
||||
} |
||||
} |
@ -1,196 +0,0 @@
@@ -1,196 +0,0 @@
|
||||
/* |
||||
* 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.AssignmentTestUtils.UUID_4; |
||||
import static org.apache.kafka.streams.processor.internals.assignment.RankedClient.buildClientRankingsByTask; |
||||
import static org.apache.kafka.streams.processor.internals.assignment.RankedClient.tasksToCaughtUpClients; |
||||
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.SortedMap; |
||||
import java.util.SortedSet; |
||||
import java.util.TreeMap; |
||||
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()); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldReturnTasksToCaughtUpClients() { |
||||
final SortedMap<TaskId, SortedSet<RankedClient>> statefulTasksToRankedCandidates = new TreeMap<>(); |
||||
statefulTasksToRankedCandidates.put( |
||||
TASK_0_0, |
||||
mkSortedSet( |
||||
new RankedClient(UUID_1, Task.LATEST_OFFSET), |
||||
new RankedClient(UUID_2, 0L), |
||||
new RankedClient(UUID_3, 1L), |
||||
new RankedClient(UUID_4, 1000L) |
||||
)); |
||||
final SortedSet<UUID> expectedCaughtUpClients = mkSortedSet(UUID_1, UUID_2); |
||||
|
||||
final Map<TaskId, SortedSet<UUID>> tasksToCaughtUpClients = tasksToCaughtUpClients(statefulTasksToRankedCandidates); |
||||
|
||||
assertThat(tasksToCaughtUpClients.get(TASK_0_0), equalTo(expectedCaughtUpClients)); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldOnlyReturnTasksWithCaughtUpClients() { |
||||
final RankedClient rankedClient = new RankedClient(UUID_1, 1L); |
||||
|
||||
final SortedMap<TaskId, SortedSet<RankedClient>> statefulTasksToRankedCandidates = new TreeMap<>(); |
||||
statefulTasksToRankedCandidates.put(TASK_0_0, mkSortedSet(rankedClient)); |
||||
final Map<TaskId, SortedSet<UUID>> tasksToCaughtUpClients = tasksToCaughtUpClients(statefulTasksToRankedCandidates); |
||||
|
||||
assertTrue(tasksToCaughtUpClients.isEmpty()); |
||||
} |
||||
} |
@ -1,126 +0,0 @@
@@ -1,126 +0,0 @@
|
||||
/* |
||||
* 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.singletonList; |
||||
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_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_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.AssignmentTestUtils.getClientStatesMap; |
||||
import static org.hamcrest.CoreMatchers.equalTo; |
||||
import static org.hamcrest.MatcherAssert.assertThat; |
||||
import static org.junit.Assert.assertNull; |
||||
|
||||
import java.util.Map; |
||||
import java.util.UUID; |
||||
import java.util.function.BiFunction; |
||||
import org.apache.kafka.streams.processor.TaskId; |
||||
import org.junit.Test; |
||||
|
||||
public class ValidClientsByTaskLoadQueueTest { |
||||
|
||||
private static final TaskId DUMMY_TASK = new TaskId(0, 0); |
||||
|
||||
private final ClientState client1 = new ClientState(1); |
||||
private final ClientState client2 = new ClientState(1); |
||||
private final ClientState client3 = new ClientState(1); |
||||
|
||||
private final BiFunction<UUID, TaskId, Boolean> alwaysTrue = (client, task) -> true; |
||||
private final BiFunction<UUID, TaskId, Boolean> alwaysFalse = (client, task) -> false; |
||||
|
||||
private ValidClientsByTaskLoadQueue queue; |
||||
|
||||
private Map<UUID, ClientState> clientStates; |
||||
|
||||
@Test |
||||
public void shouldReturnOnlyClient() { |
||||
clientStates = getClientStatesMap(client1); |
||||
queue = new ValidClientsByTaskLoadQueue(clientStates, alwaysTrue); |
||||
queue.offerAll(clientStates.keySet()); |
||||
|
||||
assertThat(queue.poll(DUMMY_TASK), equalTo(UUID_1)); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldReturnNull() { |
||||
clientStates = getClientStatesMap(client1); |
||||
queue = new ValidClientsByTaskLoadQueue(clientStates, alwaysFalse); |
||||
queue.offerAll(clientStates.keySet()); |
||||
|
||||
assertNull(queue.poll(DUMMY_TASK)); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldReturnLeastLoadedClient() { |
||||
clientStates = getClientStatesMap(client1, client2, client3); |
||||
queue = new ValidClientsByTaskLoadQueue(clientStates, alwaysTrue); |
||||
|
||||
client1.assignActive(TASK_0_0); |
||||
client2.assignActiveTasks(asList(TASK_0_1, TASK_1_1)); |
||||
client3.assignActiveTasks(asList(TASK_0_2, TASK_1_2, TASK_2_2)); |
||||
|
||||
queue.offerAll(clientStates.keySet()); |
||||
|
||||
assertThat(queue.poll(DUMMY_TASK), equalTo(UUID_1)); |
||||
assertThat(queue.poll(DUMMY_TASK), equalTo(UUID_2)); |
||||
assertThat(queue.poll(DUMMY_TASK), equalTo(UUID_3)); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldNotRetainDuplicates() { |
||||
clientStates = getClientStatesMap(client1); |
||||
queue = new ValidClientsByTaskLoadQueue(clientStates, alwaysTrue); |
||||
|
||||
queue.offerAll(clientStates.keySet()); |
||||
queue.offer(UUID_1); |
||||
|
||||
assertThat(queue.poll(DUMMY_TASK), equalTo(UUID_1)); |
||||
assertNull(queue.poll(DUMMY_TASK)); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldOnlyReturnValidClients() { |
||||
clientStates = getClientStatesMap(client1, client2); |
||||
queue = new ValidClientsByTaskLoadQueue(clientStates, (client, task) -> client.equals(UUID_1)); |
||||
|
||||
queue.offerAll(clientStates.keySet()); |
||||
|
||||
assertThat(queue.poll(DUMMY_TASK, 2), equalTo(singletonList(UUID_1))); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldReturnUpToNumClients() { |
||||
clientStates = getClientStatesMap(client1, client2, client3); |
||||
queue = new ValidClientsByTaskLoadQueue(clientStates, alwaysTrue); |
||||
|
||||
client1.assignActive(TASK_0_0); |
||||
client2.assignActiveTasks(asList(TASK_0_1, TASK_1_1)); |
||||
client3.assignActiveTasks(asList(TASK_0_2, TASK_1_2, TASK_2_2)); |
||||
|
||||
queue.offerAll(clientStates.keySet()); |
||||
|
||||
assertThat(queue.poll(DUMMY_TASK, 2), equalTo(asList(UUID_1, UUID_2))); |
||||
} |
||||
} |
Loading…
Reference in new issue