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