Committed by
Gerrit Code Review
AsyncLeaderElector APIs in support for mastership balancing
Change-Id: Ia235c6a18c54490dc49ca13e2caebf70b750dbc7
Showing
9 changed files
with
346 additions
and
13 deletions
... | @@ -60,6 +60,16 @@ public class DefaultLeaderElector extends Synchronous<AsyncLeaderElector> implem | ... | @@ -60,6 +60,16 @@ public class DefaultLeaderElector extends Synchronous<AsyncLeaderElector> implem |
60 | } | 60 | } |
61 | 61 | ||
62 | @Override | 62 | @Override |
63 | + public boolean promote(String topic, NodeId nodeId) { | ||
64 | + return complete(asyncElector.promote(topic, nodeId)); | ||
65 | + } | ||
66 | + | ||
67 | + @Override | ||
68 | + public void evict(NodeId nodeId) { | ||
69 | + complete(asyncElector.evict(nodeId)); | ||
70 | + } | ||
71 | + | ||
72 | + @Override | ||
63 | public Leadership getLeadership(String topic) { | 73 | public Leadership getLeadership(String topic) { |
64 | return complete(asyncElector.getLeadership(topic)); | 74 | return complete(asyncElector.getLeadership(topic)); |
65 | } | 75 | } | ... | ... |
... | @@ -76,6 +76,28 @@ public interface AsyncLeaderElector extends DistributedPrimitive { | ... | @@ -76,6 +76,28 @@ public interface AsyncLeaderElector extends DistributedPrimitive { |
76 | CompletableFuture<Boolean> anoint(String topic, NodeId nodeId); | 76 | CompletableFuture<Boolean> anoint(String topic, NodeId nodeId); |
77 | 77 | ||
78 | /** | 78 | /** |
79 | + * Attempts to evict a node from all leadership elections it is registered for. | ||
80 | + * <p> | ||
81 | + * If the node is the current leader for a topic, this call will promote the next top candidate | ||
82 | + * (if one exists) to leadership. | ||
83 | + * | ||
84 | + * @param nodeId node instance identifier | ||
85 | + * @return CompletableFuture that is completed when the operation is done. | ||
86 | + */ | ||
87 | + CompletableFuture<Void> evict(NodeId nodeId); | ||
88 | + | ||
89 | + /** | ||
90 | + * Attempts to promote a node to top of candidate list without displacing the current leader. | ||
91 | + * | ||
92 | + * @param topic leadership topic | ||
93 | + * @param nodeId instance identifier of the new top candidate | ||
94 | + * @return CompletableFuture that is completed with a boolean when the operation is done. Boolean is true if | ||
95 | + * node is now the top candidate. This operation can fail (i.e. return false) if the node | ||
96 | + * is not registered to run for election for the topic. | ||
97 | + */ | ||
98 | + CompletableFuture<Boolean> promote(String topic, NodeId nodeId); | ||
99 | + | ||
100 | + /** | ||
79 | * Returns the {@link Leadership} for the specified topic. | 101 | * Returns the {@link Leadership} for the specified topic. |
80 | * @param topic leadership topic | 102 | * @param topic leadership topic |
81 | * @return CompletableFuture that is completed with the current Leadership state of the topic | 103 | * @return CompletableFuture that is completed with the current Leadership state of the topic | ... | ... |
... | @@ -58,6 +58,25 @@ public interface LeaderElector extends DistributedPrimitive { | ... | @@ -58,6 +58,25 @@ public interface LeaderElector extends DistributedPrimitive { |
58 | boolean anoint(String topic, NodeId nodeId); | 58 | boolean anoint(String topic, NodeId nodeId); |
59 | 59 | ||
60 | /** | 60 | /** |
61 | + * Attempts to promote a node to top of candidate list. | ||
62 | + * | ||
63 | + * @param topic leadership topic | ||
64 | + * @param nodeId instance identifier of the new top candidate | ||
65 | + * @return {@code true} if node is now the top candidate. This operation can fail (i.e. return | ||
66 | + * {@code false}) if the node is not registered to run for election for the topic. | ||
67 | + */ | ||
68 | + boolean promote(String topic, NodeId nodeId); | ||
69 | + | ||
70 | + /** | ||
71 | + * Attempts to evict a node from all leadership elections it is registered for. | ||
72 | + * <p> | ||
73 | + * If the node the current leader for a topic, this call will force the next candidate (if one exists) | ||
74 | + * to be promoted to leadership. | ||
75 | + * @param nodeId node instance identifier | ||
76 | + */ | ||
77 | + void evict(NodeId nodeId); | ||
78 | + | ||
79 | + /** | ||
61 | * Returns the {@link Leadership} for the specified topic. | 80 | * Returns the {@link Leadership} for the specified topic. |
62 | * @param topic leadership topic | 81 | * @param topic leadership topic |
63 | * @return current Leadership state of the topic | 82 | * @return current Leadership state of the topic | ... | ... |
... | @@ -128,8 +128,7 @@ public class NewDistributedLeadershipStore | ... | @@ -128,8 +128,7 @@ public class NewDistributedLeadershipStore |
128 | 128 | ||
129 | @Override | 129 | @Override |
130 | public void removeRegistration(NodeId nodeId) { | 130 | public void removeRegistration(NodeId nodeId) { |
131 | - // TODO | 131 | + leaderElector.evict(nodeId); |
132 | - throw new UnsupportedOperationException(); | ||
133 | } | 132 | } |
134 | 133 | ||
135 | @Override | 134 | @Override |
... | @@ -139,8 +138,7 @@ public class NewDistributedLeadershipStore | ... | @@ -139,8 +138,7 @@ public class NewDistributedLeadershipStore |
139 | 138 | ||
140 | @Override | 139 | @Override |
141 | public boolean makeTopCandidate(String topic, NodeId nodeId) { | 140 | public boolean makeTopCandidate(String topic, NodeId nodeId) { |
142 | - // TODO | 141 | + return leaderElector.promote(topic, nodeId); |
143 | - throw new UnsupportedOperationException(); | ||
144 | } | 142 | } |
145 | 143 | ||
146 | @Override | 144 | @Override | ... | ... |
... | @@ -70,6 +70,18 @@ public class PartitionedAsyncLeaderElector implements AsyncLeaderElector { | ... | @@ -70,6 +70,18 @@ public class PartitionedAsyncLeaderElector implements AsyncLeaderElector { |
70 | } | 70 | } |
71 | 71 | ||
72 | @Override | 72 | @Override |
73 | + public CompletableFuture<Boolean> promote(String topic, NodeId nodeId) { | ||
74 | + return getLeaderElector(topic).promote(topic, nodeId); | ||
75 | + } | ||
76 | + | ||
77 | + @Override | ||
78 | + public CompletableFuture<Void> evict(NodeId nodeId) { | ||
79 | + return CompletableFuture.allOf(getLeaderElectors().stream() | ||
80 | + .map(le -> le.evict(nodeId)) | ||
81 | + .toArray(CompletableFuture[]::new)); | ||
82 | + } | ||
83 | + | ||
84 | + @Override | ||
73 | public CompletableFuture<Leadership> getLeadership(String topic) { | 85 | public CompletableFuture<Leadership> getLeadership(String topic) { |
74 | return getLeaderElector(topic).getLeadership(topic); | 86 | return getLeaderElector(topic).getLeadership(topic); |
75 | } | 87 | } | ... | ... |
... | @@ -20,6 +20,7 @@ import io.atomix.copycat.client.CopycatClient; | ... | @@ -20,6 +20,7 @@ import io.atomix.copycat.client.CopycatClient; |
20 | import io.atomix.resource.Resource; | 20 | import io.atomix.resource.Resource; |
21 | import io.atomix.resource.ResourceTypeInfo; | 21 | import io.atomix.resource.ResourceTypeInfo; |
22 | 22 | ||
23 | +import java.util.List; | ||
23 | import java.util.Map; | 24 | import java.util.Map; |
24 | import java.util.Set; | 25 | import java.util.Set; |
25 | import java.util.concurrent.CompletableFuture; | 26 | import java.util.concurrent.CompletableFuture; |
... | @@ -43,6 +44,7 @@ public class AtomixLeaderElector extends Resource<AtomixLeaderElector> | ... | @@ -43,6 +44,7 @@ public class AtomixLeaderElector extends Resource<AtomixLeaderElector> |
43 | private final Set<Consumer<Change<Leadership>>> leadershipChangeListeners = | 44 | private final Set<Consumer<Change<Leadership>>> leadershipChangeListeners = |
44 | Sets.newConcurrentHashSet(); | 45 | Sets.newConcurrentHashSet(); |
45 | 46 | ||
47 | + public static final String CHANGE_SUBJECT = "changeEvents"; | ||
46 | private Listener<Change<Leadership>> listener; | 48 | private Listener<Change<Leadership>> listener; |
47 | 49 | ||
48 | public AtomixLeaderElector(CopycatClient client, Resource.Options options) { | 50 | public AtomixLeaderElector(CopycatClient client, Resource.Options options) { |
... | @@ -57,13 +59,13 @@ public class AtomixLeaderElector extends Resource<AtomixLeaderElector> | ... | @@ -57,13 +59,13 @@ public class AtomixLeaderElector extends Resource<AtomixLeaderElector> |
57 | @Override | 59 | @Override |
58 | public CompletableFuture<AtomixLeaderElector> open() { | 60 | public CompletableFuture<AtomixLeaderElector> open() { |
59 | return super.open().thenApply(result -> { | 61 | return super.open().thenApply(result -> { |
60 | - client.onEvent("change", this::handleEvent); | 62 | + client.onEvent(CHANGE_SUBJECT, this::handleEvent); |
61 | return result; | 63 | return result; |
62 | }); | 64 | }); |
63 | } | 65 | } |
64 | 66 | ||
65 | - private void handleEvent(Change<Leadership> change) { | 67 | + private void handleEvent(List<Change<Leadership>> changes) { |
66 | - leadershipChangeListeners.forEach(l -> l.accept(change)); | 68 | + changes.forEach(change -> leadershipChangeListeners.forEach(l -> l.accept(change))); |
67 | } | 69 | } |
68 | 70 | ||
69 | @Override | 71 | @Override |
... | @@ -82,6 +84,16 @@ public class AtomixLeaderElector extends Resource<AtomixLeaderElector> | ... | @@ -82,6 +84,16 @@ public class AtomixLeaderElector extends Resource<AtomixLeaderElector> |
82 | } | 84 | } |
83 | 85 | ||
84 | @Override | 86 | @Override |
87 | + public CompletableFuture<Boolean> promote(String topic, NodeId nodeId) { | ||
88 | + return submit(new AtomixLeaderElectorCommands.Promote(topic, nodeId)); | ||
89 | + } | ||
90 | + | ||
91 | + @Override | ||
92 | + public CompletableFuture<Void> evict(NodeId nodeId) { | ||
93 | + return submit(new AtomixLeaderElectorCommands.Evict(nodeId)); | ||
94 | + } | ||
95 | + | ||
96 | + @Override | ||
85 | public CompletableFuture<Leadership> getLeadership(String topic) { | 97 | public CompletableFuture<Leadership> getLeadership(String topic) { |
86 | return submit(new AtomixLeaderElectorCommands.GetLeadership(topic)); | 98 | return submit(new AtomixLeaderElectorCommands.GetLeadership(topic)); |
87 | } | 99 | } | ... | ... |
... | @@ -346,6 +346,102 @@ public final class AtomixLeaderElectorCommands { | ... | @@ -346,6 +346,102 @@ public final class AtomixLeaderElectorCommands { |
346 | } | 346 | } |
347 | 347 | ||
348 | /** | 348 | /** |
349 | + * Command for administratively promote a node as top candidate. | ||
350 | + */ | ||
351 | + @SuppressWarnings("serial") | ||
352 | + public static class Promote extends ElectionCommand<Boolean> { | ||
353 | + private String topic; | ||
354 | + private NodeId nodeId; | ||
355 | + | ||
356 | + public Promote() { | ||
357 | + } | ||
358 | + | ||
359 | + public Promote(String topic, NodeId nodeId) { | ||
360 | + this.topic = topic; | ||
361 | + this.nodeId = nodeId; | ||
362 | + } | ||
363 | + | ||
364 | + /** | ||
365 | + * Returns the topic. | ||
366 | + * | ||
367 | + * @return The topic | ||
368 | + */ | ||
369 | + public String topic() { | ||
370 | + return topic; | ||
371 | + } | ||
372 | + | ||
373 | + /** | ||
374 | + * Returns the nodeId to make top candidate. | ||
375 | + * | ||
376 | + * @return The nodeId | ||
377 | + */ | ||
378 | + public NodeId nodeId() { | ||
379 | + return nodeId; | ||
380 | + } | ||
381 | + | ||
382 | + @Override | ||
383 | + public String toString() { | ||
384 | + return MoreObjects.toStringHelper(getClass()) | ||
385 | + .add("topic", topic) | ||
386 | + .add("nodeId", nodeId) | ||
387 | + .toString(); | ||
388 | + } | ||
389 | + | ||
390 | + @Override | ||
391 | + public void writeObject(BufferOutput<?> buffer, Serializer serializer) { | ||
392 | + buffer.writeString(topic); | ||
393 | + buffer.writeString(nodeId.toString()); | ||
394 | + } | ||
395 | + | ||
396 | + @Override | ||
397 | + public void readObject(BufferInput<?> buffer, Serializer serializer) { | ||
398 | + topic = buffer.readString(); | ||
399 | + nodeId = new NodeId(buffer.readString()); | ||
400 | + } | ||
401 | + } | ||
402 | + | ||
403 | + /** | ||
404 | + * Command for administratively evicting a node from all leadership topics. | ||
405 | + */ | ||
406 | + @SuppressWarnings("serial") | ||
407 | + public static class Evict extends ElectionCommand<Void> { | ||
408 | + private NodeId nodeId; | ||
409 | + | ||
410 | + public Evict() { | ||
411 | + } | ||
412 | + | ||
413 | + public Evict(NodeId nodeId) { | ||
414 | + this.nodeId = nodeId; | ||
415 | + } | ||
416 | + | ||
417 | + /** | ||
418 | + * Returns the node identifier. | ||
419 | + * | ||
420 | + * @return The nodeId | ||
421 | + */ | ||
422 | + public NodeId nodeId() { | ||
423 | + return nodeId; | ||
424 | + } | ||
425 | + | ||
426 | + @Override | ||
427 | + public String toString() { | ||
428 | + return MoreObjects.toStringHelper(getClass()) | ||
429 | + .add("nodeId", nodeId) | ||
430 | + .toString(); | ||
431 | + } | ||
432 | + | ||
433 | + @Override | ||
434 | + public void writeObject(BufferOutput<?> buffer, Serializer serializer) { | ||
435 | + buffer.writeString(nodeId.toString()); | ||
436 | + } | ||
437 | + | ||
438 | + @Override | ||
439 | + public void readObject(BufferInput<?> buffer, Serializer serializer) { | ||
440 | + nodeId = new NodeId(buffer.readString()); | ||
441 | + } | ||
442 | + } | ||
443 | + | ||
444 | + /** | ||
349 | * Map command type resolver. | 445 | * Map command type resolver. |
350 | */ | 446 | */ |
351 | public static class TypeResolver implements SerializableTypeResolver { | 447 | public static class TypeResolver implements SerializableTypeResolver { |
... | @@ -359,6 +455,8 @@ public final class AtomixLeaderElectorCommands { | ... | @@ -359,6 +455,8 @@ public final class AtomixLeaderElectorCommands { |
359 | registry.register(GetLeadership.class, -866); | 455 | registry.register(GetLeadership.class, -866); |
360 | registry.register(Listen.class, -867); | 456 | registry.register(Listen.class, -867); |
361 | registry.register(Unlisten.class, -868); | 457 | registry.register(Unlisten.class, -868); |
458 | + registry.register(Promote.class, -869); | ||
459 | + registry.register(Evict.class, -870); | ||
362 | } | 460 | } |
363 | } | 461 | } |
364 | } | 462 | } | ... | ... |
... | @@ -43,10 +43,12 @@ import org.onosproject.cluster.Leadership; | ... | @@ -43,10 +43,12 @@ import org.onosproject.cluster.Leadership; |
43 | import org.onosproject.cluster.NodeId; | 43 | import org.onosproject.cluster.NodeId; |
44 | import org.onosproject.event.Change; | 44 | import org.onosproject.event.Change; |
45 | import org.onosproject.store.primitives.resources.impl.AtomixLeaderElectorCommands.Anoint; | 45 | import org.onosproject.store.primitives.resources.impl.AtomixLeaderElectorCommands.Anoint; |
46 | +import org.onosproject.store.primitives.resources.impl.AtomixLeaderElectorCommands.Evict; | ||
46 | import org.onosproject.store.primitives.resources.impl.AtomixLeaderElectorCommands.GetAllLeaderships; | 47 | import org.onosproject.store.primitives.resources.impl.AtomixLeaderElectorCommands.GetAllLeaderships; |
47 | import org.onosproject.store.primitives.resources.impl.AtomixLeaderElectorCommands.GetElectedTopics; | 48 | import org.onosproject.store.primitives.resources.impl.AtomixLeaderElectorCommands.GetElectedTopics; |
48 | import org.onosproject.store.primitives.resources.impl.AtomixLeaderElectorCommands.GetLeadership; | 49 | import org.onosproject.store.primitives.resources.impl.AtomixLeaderElectorCommands.GetLeadership; |
49 | import org.onosproject.store.primitives.resources.impl.AtomixLeaderElectorCommands.Listen; | 50 | import org.onosproject.store.primitives.resources.impl.AtomixLeaderElectorCommands.Listen; |
51 | +import org.onosproject.store.primitives.resources.impl.AtomixLeaderElectorCommands.Promote; | ||
50 | import org.onosproject.store.primitives.resources.impl.AtomixLeaderElectorCommands.Run; | 52 | import org.onosproject.store.primitives.resources.impl.AtomixLeaderElectorCommands.Run; |
51 | import org.onosproject.store.primitives.resources.impl.AtomixLeaderElectorCommands.Unlisten; | 53 | import org.onosproject.store.primitives.resources.impl.AtomixLeaderElectorCommands.Unlisten; |
52 | import org.onosproject.store.primitives.resources.impl.AtomixLeaderElectorCommands.Withdraw; | 54 | import org.onosproject.store.primitives.resources.impl.AtomixLeaderElectorCommands.Withdraw; |
... | @@ -86,6 +88,8 @@ public class AtomixLeaderElectorState extends ResourceStateMachine | ... | @@ -86,6 +88,8 @@ public class AtomixLeaderElectorState extends ResourceStateMachine |
86 | executor.register(Run.class, this::run); | 88 | executor.register(Run.class, this::run); |
87 | executor.register(Withdraw.class, this::withdraw); | 89 | executor.register(Withdraw.class, this::withdraw); |
88 | executor.register(Anoint.class, this::anoint); | 90 | executor.register(Anoint.class, this::anoint); |
91 | + executor.register(Promote.class, this::promote); | ||
92 | + executor.register(Evict.class, this::evict); | ||
89 | // Queries | 93 | // Queries |
90 | executor.register(GetLeadership.class, this::leadership); | 94 | executor.register(GetLeadership.class, this::leadership); |
91 | executor.register(GetAllLeaderships.class, this::allLeaderships); | 95 | executor.register(GetAllLeaderships.class, this::allLeaderships); |
... | @@ -93,8 +97,16 @@ public class AtomixLeaderElectorState extends ResourceStateMachine | ... | @@ -93,8 +97,16 @@ public class AtomixLeaderElectorState extends ResourceStateMachine |
93 | } | 97 | } |
94 | 98 | ||
95 | private void notifyLeadershipChange(Leadership previousLeadership, Leadership newLeadership) { | 99 | private void notifyLeadershipChange(Leadership previousLeadership, Leadership newLeadership) { |
96 | - Change<Leadership> change = new Change<>(previousLeadership, newLeadership); | 100 | + notifyLeadershipChanges(Arrays.asList(new Change<>(previousLeadership, newLeadership))); |
97 | - listeners.values().forEach(listener -> listener.session().publish("change", change)); | 101 | + } |
102 | + | ||
103 | + private void notifyLeadershipChanges(List<Change<Leadership>> changes) { | ||
104 | + if (changes.isEmpty()) { | ||
105 | + return; | ||
106 | + } | ||
107 | + listeners.values() | ||
108 | + .forEach(listener -> listener.session() | ||
109 | + .publish(AtomixLeaderElector.CHANGE_SUBJECT, changes)); | ||
98 | } | 110 | } |
99 | 111 | ||
100 | @Override | 112 | @Override |
... | @@ -206,6 +218,53 @@ public class AtomixLeaderElectorState extends ResourceStateMachine | ... | @@ -206,6 +218,53 @@ public class AtomixLeaderElectorState extends ResourceStateMachine |
206 | } | 218 | } |
207 | 219 | ||
208 | /** | 220 | /** |
221 | + * Applies an {@link AtomixLeaderElectorCommands.Promote} commit. | ||
222 | + * @param commit promote commit | ||
223 | + * @return {@code true} if changes desired end state is achieved. | ||
224 | + */ | ||
225 | + public boolean promote(Commit<? extends Promote> commit) { | ||
226 | + try { | ||
227 | + String topic = commit.operation().topic(); | ||
228 | + NodeId nodeId = commit.operation().nodeId(); | ||
229 | + Leadership oldLeadership = leadership(topic); | ||
230 | + if (oldLeadership == null || !oldLeadership.candidates().contains(nodeId)) { | ||
231 | + return false; | ||
232 | + } | ||
233 | + elections.computeIfPresent(topic, (k, v) -> new ElectionState(v).promote(commit.operation().nodeId())); | ||
234 | + Leadership newLeadership = leadership(topic); | ||
235 | + if (!Objects.equal(oldLeadership, newLeadership)) { | ||
236 | + notifyLeadershipChange(oldLeadership, newLeadership); | ||
237 | + } | ||
238 | + return true; | ||
239 | + } finally { | ||
240 | + commit.close(); | ||
241 | + } | ||
242 | + } | ||
243 | + | ||
244 | + /** | ||
245 | + * Applies an {@link AtomixLeaderElectorCommands.Evict} commit. | ||
246 | + * @param commit evict commit | ||
247 | + */ | ||
248 | + public void evict(Commit<? extends Evict> commit) { | ||
249 | + try { | ||
250 | + List<Change<Leadership>> changes = Lists.newLinkedList(); | ||
251 | + NodeId nodeId = commit.operation().nodeId(); | ||
252 | + Set<String> topics = Maps.filterValues(elections, e -> e.candidates().contains(nodeId)).keySet(); | ||
253 | + topics.forEach(topic -> { | ||
254 | + Leadership oldLeadership = leadership(topic); | ||
255 | + elections.compute(topic, (k, v) -> v.evict(nodeId, termCounter(topic)::incrementAndGet)); | ||
256 | + Leadership newLeadership = leadership(topic); | ||
257 | + if (!Objects.equal(oldLeadership, newLeadership)) { | ||
258 | + changes.add(new Change<>(oldLeadership, newLeadership)); | ||
259 | + } | ||
260 | + }); | ||
261 | + notifyLeadershipChanges(changes); | ||
262 | + } finally { | ||
263 | + commit.close(); | ||
264 | + } | ||
265 | + } | ||
266 | + | ||
267 | + /** | ||
209 | * Applies an {@link AtomixLeaderElectorCommands.GetLeadership} commit. | 268 | * Applies an {@link AtomixLeaderElectorCommands.GetLeadership} commit. |
210 | * @param commit GetLeadership commit | 269 | * @param commit GetLeadership commit |
211 | * @return leader | 270 | * @return leader |
... | @@ -362,6 +421,31 @@ public class AtomixLeaderElectorState extends ResourceStateMachine | ... | @@ -362,6 +421,31 @@ public class AtomixLeaderElectorState extends ResourceStateMachine |
362 | } | 421 | } |
363 | } | 422 | } |
364 | 423 | ||
424 | + public ElectionState evict(NodeId nodeId, Supplier<Long> termCounter) { | ||
425 | + Optional<Registration> registration = | ||
426 | + registrations.stream().filter(r -> r.nodeId.equals(nodeId)).findFirst(); | ||
427 | + if (registration.isPresent()) { | ||
428 | + List<Registration> updatedRegistrations = | ||
429 | + registrations.stream() | ||
430 | + .filter(r -> !r.nodeId().equals(nodeId)) | ||
431 | + .collect(Collectors.toList()); | ||
432 | + if (leader.nodeId().equals(nodeId)) { | ||
433 | + if (updatedRegistrations.size() > 0) { | ||
434 | + return new ElectionState(updatedRegistrations, | ||
435 | + updatedRegistrations.get(0), | ||
436 | + termCounter.get(), | ||
437 | + System.currentTimeMillis()); | ||
438 | + } else { | ||
439 | + return new ElectionState(updatedRegistrations, null, term, termStartTime); | ||
440 | + } | ||
441 | + } else { | ||
442 | + return new ElectionState(updatedRegistrations, leader, term, termStartTime); | ||
443 | + } | ||
444 | + } else { | ||
445 | + return this; | ||
446 | + } | ||
447 | + } | ||
448 | + | ||
365 | public boolean isDuplicate(Registration registration) { | 449 | public boolean isDuplicate(Registration registration) { |
366 | return registrations.stream().anyMatch(r -> r.sessionId() == registration.sessionId()); | 450 | return registrations.stream().anyMatch(r -> r.sessionId() == registration.sessionId()); |
367 | } | 451 | } |
... | @@ -406,6 +490,23 @@ public class AtomixLeaderElectorState extends ResourceStateMachine | ... | @@ -406,6 +490,23 @@ public class AtomixLeaderElectorState extends ResourceStateMachine |
406 | return this; | 490 | return this; |
407 | } | 491 | } |
408 | } | 492 | } |
493 | + | ||
494 | + public ElectionState promote(NodeId nodeId) { | ||
495 | + Registration registration = registrations.stream() | ||
496 | + .filter(r -> r.nodeId().equals(nodeId)) | ||
497 | + .findFirst() | ||
498 | + .orElse(null); | ||
499 | + List<Registration> updatedRegistrations = Lists.newLinkedList(); | ||
500 | + updatedRegistrations.add(registration); | ||
501 | + registrations.stream() | ||
502 | + .filter(r -> !r.nodeId().equals(nodeId)) | ||
503 | + .forEach(updatedRegistrations::add); | ||
504 | + return new ElectionState(updatedRegistrations, | ||
505 | + leader, | ||
506 | + term, | ||
507 | + termStartTime); | ||
508 | + | ||
509 | + } | ||
409 | } | 510 | } |
410 | 511 | ||
411 | @Override | 512 | @Override | ... | ... |
... | @@ -51,10 +51,10 @@ public class AtomixLeaderElectorTest extends AtomixTestBase { | ... | @@ -51,10 +51,10 @@ public class AtomixLeaderElectorTest extends AtomixTestBase { |
51 | public void testRun() throws Throwable { | 51 | public void testRun() throws Throwable { |
52 | leaderElectorRunTests(1); | 52 | leaderElectorRunTests(1); |
53 | clearTests(); | 53 | clearTests(); |
54 | -// leaderElectorRunTests(2); | 54 | + leaderElectorRunTests(2); |
55 | -// clearTests(); | 55 | + clearTests(); |
56 | -// leaderElectorRunTests(3); | 56 | + leaderElectorRunTests(3); |
57 | -// clearTests(); | 57 | + clearTests(); |
58 | } | 58 | } |
59 | 59 | ||
60 | private void leaderElectorRunTests(int numServers) throws Throwable { | 60 | private void leaderElectorRunTests(int numServers) throws Throwable { |
... | @@ -183,6 +183,63 @@ public class AtomixLeaderElectorTest extends AtomixTestBase { | ... | @@ -183,6 +183,63 @@ public class AtomixLeaderElectorTest extends AtomixTestBase { |
183 | } | 183 | } |
184 | 184 | ||
185 | @Test | 185 | @Test |
186 | + public void testPromote() throws Throwable { | ||
187 | + leaderElectorPromoteTests(1); | ||
188 | + clearTests(); | ||
189 | + leaderElectorPromoteTests(2); | ||
190 | + clearTests(); | ||
191 | + leaderElectorPromoteTests(3); | ||
192 | + clearTests(); | ||
193 | + } | ||
194 | + | ||
195 | + private void leaderElectorPromoteTests(int numServers) throws Throwable { | ||
196 | + createCopycatServers(numServers); | ||
197 | + Atomix client1 = createAtomixClient(); | ||
198 | + AtomixLeaderElector elector1 = client1.get("test-elector", AtomixLeaderElector.class).join(); | ||
199 | + Atomix client2 = createAtomixClient(); | ||
200 | + AtomixLeaderElector elector2 = client2.get("test-elector", AtomixLeaderElector.class).join(); | ||
201 | + Atomix client3 = createAtomixClient(); | ||
202 | + AtomixLeaderElector elector3 = client3.get("test-elector", AtomixLeaderElector.class).join(); | ||
203 | + elector1.run("foo", node1).join(); | ||
204 | + elector2.run("foo", node2).join(); | ||
205 | + | ||
206 | + LeaderEventListener listener1 = new LeaderEventListener(); | ||
207 | + elector1.addChangeListener(listener1).join(); | ||
208 | + LeaderEventListener listener2 = new LeaderEventListener(); | ||
209 | + elector2.addChangeListener(listener2).join(); | ||
210 | + LeaderEventListener listener3 = new LeaderEventListener(); | ||
211 | + elector3.addChangeListener(listener3).join(); | ||
212 | + | ||
213 | + elector3.promote("foo", node3).thenAccept(result -> { | ||
214 | + assertFalse(result); | ||
215 | + }).join(); | ||
216 | + | ||
217 | + assertFalse(listener1.hasEvent()); | ||
218 | + assertFalse(listener2.hasEvent()); | ||
219 | + assertFalse(listener3.hasEvent()); | ||
220 | + | ||
221 | + elector3.run("foo", node3).join(); | ||
222 | + | ||
223 | + listener1.clearEvents(); | ||
224 | + listener2.clearEvents(); | ||
225 | + listener3.clearEvents(); | ||
226 | + | ||
227 | + elector3.promote("foo", node3).thenAccept(result -> { | ||
228 | + assertTrue(result); | ||
229 | + }).join(); | ||
230 | + | ||
231 | + listener1.nextEvent().thenAccept(result -> { | ||
232 | + assertEquals(node3, result.newValue().candidates().get(0)); | ||
233 | + }).join(); | ||
234 | + listener2.nextEvent().thenAccept(result -> { | ||
235 | + assertEquals(node3, result.newValue().candidates().get(0)); | ||
236 | + }).join(); | ||
237 | + listener3.nextEvent().thenAccept(result -> { | ||
238 | + assertEquals(node3, result.newValue().candidates().get(0)); | ||
239 | + }).join(); | ||
240 | + } | ||
241 | + | ||
242 | + @Test | ||
186 | public void testLeaderSessionClose() throws Throwable { | 243 | public void testLeaderSessionClose() throws Throwable { |
187 | leaderElectorLeaderSessionCloseTests(1); | 244 | leaderElectorLeaderSessionCloseTests(1); |
188 | clearTests(); | 245 | clearTests(); |
... | @@ -325,6 +382,10 @@ public class AtomixLeaderElectorTest extends AtomixTestBase { | ... | @@ -325,6 +382,10 @@ public class AtomixLeaderElectorTest extends AtomixTestBase { |
325 | return !eventQueue.isEmpty(); | 382 | return !eventQueue.isEmpty(); |
326 | } | 383 | } |
327 | 384 | ||
385 | + public void clearEvents() { | ||
386 | + eventQueue.clear(); | ||
387 | + } | ||
388 | + | ||
328 | public CompletableFuture<Change<Leadership>> nextEvent() { | 389 | public CompletableFuture<Change<Leadership>> nextEvent() { |
329 | synchronized (this) { | 390 | synchronized (this) { |
330 | if (eventQueue.isEmpty()) { | 391 | if (eventQueue.isEmpty()) { | ... | ... |
-
Please register or login to post a comment