Ayaka Koshibe
Committed by Madan Jampani

Create local storage for topic candidates mapping. This also includes:

 - using Optional in Leadership, and some commenting.
 - using MutableBooleans + compute()

    part of: Device Mastership store on top of LeadershipService
    Reference: ONOS-76

Conflicts:
	core/api/src/main/java/org/onosproject/cluster/LeadershipService.java
	core/store/dist/src/main/java/org/onosproject/store/consistent/impl/DistributedLeadershipManager.java

Change-Id: I7f090abb123cf23bb5126a935a6e72be00f3e3ce
...@@ -85,7 +85,7 @@ public class LeaderCommand extends AbstractShellCommand { ...@@ -85,7 +85,7 @@ public class LeaderCommand extends AbstractShellCommand {
85 } 85 }
86 86
87 private void displayCandidates(Map<String, Leadership> leaderBoard, 87 private void displayCandidates(Map<String, Leadership> leaderBoard,
88 - Map<String, List<NodeId>> candidates) { 88 + Map<String, Leadership> candidates) {
89 print("--------------------------------------------------------------"); 89 print("--------------------------------------------------------------");
90 print(FMT_C, "Topic", "Leader", "Candidates"); 90 print(FMT_C, "Topic", "Leader", "Candidates");
91 print("--------------------------------------------------------------"); 91 print("--------------------------------------------------------------");
...@@ -94,13 +94,13 @@ public class LeaderCommand extends AbstractShellCommand { ...@@ -94,13 +94,13 @@ public class LeaderCommand extends AbstractShellCommand {
94 .stream() 94 .stream()
95 .sorted(leadershipComparator) 95 .sorted(leadershipComparator)
96 .forEach(l -> { 96 .forEach(l -> {
97 - List<NodeId> list = candidates.get(l.topic()); 97 + List<NodeId> list = candidates.get(l.topic()).candidates();
98 print(FMT_C, 98 print(FMT_C,
99 l.topic(), 99 l.topic(),
100 l.leader(), 100 l.leader(),
101 - list.remove(0).toString()); 101 + list.get(0).toString());
102 // formatting hacks to get it into a table 102 // formatting hacks to get it into a table
103 - list.forEach(n -> print(FMT_C, " ", " ", n)); 103 + list.subList(1, list.size()).forEach(n -> print(FMT_C, " ", " ", n));
104 print(FMT_C, " ", " ", " "); 104 print(FMT_C, " ", " ", " ");
105 }); 105 });
106 print("--------------------------------------------------------------"); 106 print("--------------------------------------------------------------");
...@@ -139,7 +139,7 @@ public class LeaderCommand extends AbstractShellCommand { ...@@ -139,7 +139,7 @@ public class LeaderCommand extends AbstractShellCommand {
139 print("%s", json(leaderBoard)); 139 print("%s", json(leaderBoard));
140 } else { 140 } else {
141 if (showCandidates) { 141 if (showCandidates) {
142 - Map<String, List<NodeId>> candidates = leaderService.getCandidates(); 142 + Map<String, Leadership> candidates = leaderService.getCandidates();
143 displayCandidates(leaderBoard, candidates); 143 displayCandidates(leaderBoard, candidates);
144 } else { 144 } else {
145 displayLeaders(leaderBoard); 145 displayLeaders(leaderBoard);
......
...@@ -17,6 +17,7 @@ package org.onosproject.cluster; ...@@ -17,6 +17,7 @@ package org.onosproject.cluster;
17 17
18 import java.util.Objects; 18 import java.util.Objects;
19 import java.util.List; 19 import java.util.List;
20 +import java.util.Optional;
20 21
21 import org.joda.time.DateTime; 22 import org.joda.time.DateTime;
22 23
...@@ -33,19 +34,21 @@ import com.google.common.collect.ImmutableList; ...@@ -33,19 +34,21 @@ import com.google.common.collect.ImmutableList;
33 * rest in decreasing preference order.</li> 34 * rest in decreasing preference order.</li>
34 * <li>The epoch is the logical age of a Leadership construct, and should be 35 * <li>The epoch is the logical age of a Leadership construct, and should be
35 * used for comparing two Leaderships, but only of the same topic.</li> 36 * used for comparing two Leaderships, but only of the same topic.</li>
37 + * <li>The leader may be null if its accuracy can't be guaranteed. This applies
38 + * to CANDIDATES_CHANGED events and candidate board contents.</li>
36 * </ul> 39 * </ul>
37 */ 40 */
38 public class Leadership { 41 public class Leadership {
39 42
40 private final String topic; 43 private final String topic;
41 - private final NodeId leader; 44 + private final Optional<NodeId> leader;
42 private final List<NodeId> candidates; 45 private final List<NodeId> candidates;
43 private final long epoch; 46 private final long epoch;
44 private final long electedTime; 47 private final long electedTime;
45 48
46 public Leadership(String topic, NodeId leader, long epoch, long electedTime) { 49 public Leadership(String topic, NodeId leader, long epoch, long electedTime) {
47 this.topic = topic; 50 this.topic = topic;
48 - this.leader = leader; 51 + this.leader = Optional.of(leader);
49 this.candidates = ImmutableList.of(leader); 52 this.candidates = ImmutableList.of(leader);
50 this.epoch = epoch; 53 this.epoch = epoch;
51 this.electedTime = electedTime; 54 this.electedTime = electedTime;
...@@ -54,7 +57,16 @@ public class Leadership { ...@@ -54,7 +57,16 @@ public class Leadership {
54 public Leadership(String topic, NodeId leader, List<NodeId> candidates, 57 public Leadership(String topic, NodeId leader, List<NodeId> candidates,
55 long epoch, long electedTime) { 58 long epoch, long electedTime) {
56 this.topic = topic; 59 this.topic = topic;
57 - this.leader = leader; 60 + this.leader = Optional.of(leader);
61 + this.candidates = ImmutableList.copyOf(candidates);
62 + this.epoch = epoch;
63 + this.electedTime = electedTime;
64 + }
65 +
66 + public Leadership(String topic, List<NodeId> candidates,
67 + long epoch, long electedTime) {
68 + this.topic = topic;
69 + this.leader = Optional.empty();
58 this.candidates = ImmutableList.copyOf(candidates); 70 this.candidates = ImmutableList.copyOf(candidates);
59 this.epoch = epoch; 71 this.epoch = epoch;
60 this.electedTime = electedTime; 72 this.electedTime = electedTime;
...@@ -74,8 +86,9 @@ public class Leadership { ...@@ -74,8 +86,9 @@ public class Leadership {
74 * 86 *
75 * @return leader node. 87 * @return leader node.
76 */ 88 */
89 + // This will return Optional<NodeId> in the future.
77 public NodeId leader() { 90 public NodeId leader() {
78 - return leader; 91 + return leader.orElse(null);
79 } 92 }
80 93
81 /** 94 /**
......
...@@ -43,14 +43,14 @@ public class LeadershipEvent extends AbstractEvent<LeadershipEvent.Type, Leaders ...@@ -43,14 +43,14 @@ public class LeadershipEvent extends AbstractEvent<LeadershipEvent.Type, Leaders
43 LEADER_REELECTED, 43 LEADER_REELECTED,
44 44
45 /** 45 /**
46 - * Signifies that the leader has been booted and lost leadership. The event subject is the 46 + * Signifies that the leader has been booted and lost leadership. The
47 - * former leader. 47 + * event subject is the former leader.
48 */ 48 */
49 LEADER_BOOTED, 49 LEADER_BOOTED,
50 50
51 /** 51 /**
52 * Signifies that the list of candidates for leadership for a topic has 52 * Signifies that the list of candidates for leadership for a topic has
53 - * changed. 53 + * changed. This event does not guarantee accurate leader information.
54 */ 54 */
55 CANDIDATES_CHANGED 55 CANDIDATES_CHANGED
56 } 56 }
......
...@@ -76,9 +76,9 @@ public interface LeadershipService { ...@@ -76,9 +76,9 @@ public interface LeadershipService {
76 /** 76 /**
77 * Returns the candidates for all known topics. 77 * Returns the candidates for all known topics.
78 * 78 *
79 - * @return A map of topics to lists of NodeIds. 79 + * @return A mapping from topics to up-to-date candidate info.
80 */ 80 */
81 - Map<String, List<NodeId>> getCandidates(); 81 + Map<String, Leadership> getCandidates();
82 82
83 /** 83 /**
84 * Returns the candidates for a given topic. 84 * Returns the candidates for a given topic.
......
...@@ -65,7 +65,7 @@ public class LeadershipServiceAdapter implements LeadershipService { ...@@ -65,7 +65,7 @@ public class LeadershipServiceAdapter implements LeadershipService {
65 } 65 }
66 66
67 @Override 67 @Override
68 - public Map<String, List<NodeId>> getCandidates() { 68 + public Map<String, Leadership> getCandidates() {
69 return null; 69 return null;
70 } 70 }
71 71
......
...@@ -576,7 +576,7 @@ public class HazelcastLeadershipService implements LeadershipService { ...@@ -576,7 +576,7 @@ public class HazelcastLeadershipService implements LeadershipService {
576 } 576 }
577 577
578 @Override 578 @Override
579 - public Map<String, List<NodeId>> getCandidates() { 579 + public Map<String, Leadership> getCandidates() {
580 return null; 580 return null;
581 } 581 }
582 582
......
...@@ -12,6 +12,7 @@ import org.apache.felix.scr.annotations.Deactivate; ...@@ -12,6 +12,7 @@ import org.apache.felix.scr.annotations.Deactivate;
12 import org.apache.felix.scr.annotations.Reference; 12 import org.apache.felix.scr.annotations.Reference;
13 import org.apache.felix.scr.annotations.ReferenceCardinality; 13 import org.apache.felix.scr.annotations.ReferenceCardinality;
14 import org.apache.felix.scr.annotations.Service; 14 import org.apache.felix.scr.annotations.Service;
15 +import org.apache.commons.lang3.mutable.MutableBoolean;
15 import org.onlab.util.KryoNamespace; 16 import org.onlab.util.KryoNamespace;
16 import org.onosproject.cluster.ClusterService; 17 import org.onosproject.cluster.ClusterService;
17 import org.onosproject.cluster.ControllerNode; 18 import org.onosproject.cluster.ControllerNode;
...@@ -88,6 +89,7 @@ public class DistributedLeadershipManager implements LeadershipService { ...@@ -88,6 +89,7 @@ public class DistributedLeadershipManager implements LeadershipService {
88 private AbstractListenerRegistry<LeadershipEvent, LeadershipEventListener> 89 private AbstractListenerRegistry<LeadershipEvent, LeadershipEventListener>
89 listenerRegistry; 90 listenerRegistry;
90 private final Map<String, Leadership> leaderBoard = Maps.newConcurrentMap(); 91 private final Map<String, Leadership> leaderBoard = Maps.newConcurrentMap();
92 + private final Map<String, Leadership> candidateBoard = Maps.newConcurrentMap();
91 private NodeId localNodeId; 93 private NodeId localNodeId;
92 94
93 private Set<String> activeTopics = Sets.newConcurrentHashSet(); 95 private Set<String> activeTopics = Sets.newConcurrentHashSet();
...@@ -164,16 +166,14 @@ public class DistributedLeadershipManager implements LeadershipService { ...@@ -164,16 +166,14 @@ public class DistributedLeadershipManager implements LeadershipService {
164 } 166 }
165 167
166 @Override 168 @Override
167 - public Map<String, List<NodeId>> getCandidates() { 169 + public Map<String, Leadership> getCandidates() {
168 - Map<String, List<NodeId>> candidates = Maps.newHashMap(); 170 + return ImmutableMap.copyOf(candidateBoard);
169 - candidateMap.entrySet().forEach(el -> candidates.put(el.getKey(), el.getValue().value()));
170 - return ImmutableMap.copyOf(candidates);
171 } 171 }
172 172
173 @Override 173 @Override
174 public List<NodeId> getCandidates(String path) { 174 public List<NodeId> getCandidates(String path) {
175 - Versioned<List<NodeId>> candidates = candidateMap.get(path); 175 + Leadership current = candidateBoard.get(path);
176 - return candidates == null ? ImmutableList.of() : ImmutableList.copyOf(candidates.value()); 176 + return current == null ? ImmutableList.of() : ImmutableList.copyOf(current.candidates());
177 } 177 }
178 178
179 @Override 179 @Override
...@@ -207,13 +207,21 @@ public class DistributedLeadershipManager implements LeadershipService { ...@@ -207,13 +207,21 @@ public class DistributedLeadershipManager implements LeadershipService {
207 List<NodeId> candidateList = Lists.newArrayList(candidates.value()); 207 List<NodeId> candidateList = Lists.newArrayList(candidates.value());
208 if (!candidateList.contains(localNodeId)) { 208 if (!candidateList.contains(localNodeId)) {
209 candidateList.add(localNodeId); 209 candidateList.add(localNodeId);
210 - if (!candidateMap.replace(path, candidates.version(), candidateList)) { 210 + if (candidateMap.replace(path, candidates.version(), candidateList)) {
211 + Versioned<List<NodeId>> newCandidates = candidateMap.get(path);
212 + notifyCandidateAdded(
213 + path, candidateList, newCandidates.version(), newCandidates.creationTime());
214 + } else {
211 rerunForLeadership(path); 215 rerunForLeadership(path);
212 return; 216 return;
213 } 217 }
214 } 218 }
215 } else { 219 } else {
216 - if (!(candidateMap.putIfAbsent(path, ImmutableList.of(localNodeId)) == null)) { 220 + List<NodeId> candidateList = ImmutableList.of(localNodeId);
221 + if ((candidateMap.putIfAbsent(path, candidateList) == null)) {
222 + Versioned<List<NodeId>> newCandidates = candidateMap.get(path);
223 + notifyCandidateAdded(path, candidateList, newCandidates.version(), newCandidates.creationTime());
224 + } else {
217 rerunForLeadership(path); 225 rerunForLeadership(path);
218 return; 226 return;
219 } 227 }
...@@ -247,10 +255,19 @@ public class DistributedLeadershipManager implements LeadershipService { ...@@ -247,10 +255,19 @@ public class DistributedLeadershipManager implements LeadershipService {
247 if (!candidateList.remove(localNodeId)) { 255 if (!candidateList.remove(localNodeId)) {
248 return; 256 return;
249 } 257 }
250 - boolean success = candidateList.isEmpty() 258 + boolean success = false;
251 - ? candidateMap.remove(path, candidates.version()) 259 + if (candidateList.isEmpty()) {
252 - : candidateMap.replace(path, candidates.version(), candidateList); 260 + if (candidateMap.remove(path, candidates.version())) {
253 - if (!success) { 261 + success = true;
262 + }
263 + } else {
264 + if (candidateMap.replace(path, candidates.version(), candidateList)) {
265 + success = true;
266 + }
267 + }
268 + if (success) {
269 + notifyCandidateRemoved(path, candidateList, candidates.version(), candidates.creationTime());
270 + } else {
254 log.warn("Failed to withdraw from candidates list. Will retry"); 271 log.warn("Failed to withdraw from candidates list. Will retry");
255 retryWithdraw(path); 272 retryWithdraw(path);
256 } 273 }
...@@ -321,21 +338,63 @@ public class DistributedLeadershipManager implements LeadershipService { ...@@ -321,21 +338,63 @@ public class DistributedLeadershipManager implements LeadershipService {
321 } 338 }
322 } 339 }
323 340
341 + private void notifyCandidateAdded(
342 + String path, List<NodeId> candidates, long epoch, long electedTime) {
343 + Leadership newInfo = new Leadership(path, candidates, epoch, electedTime);
344 + final MutableBoolean updated = new MutableBoolean(false);
345 + candidateBoard.compute(path, (k, current) -> {
346 + if (current == null || current.epoch() < newInfo.epoch()) {
347 + log.info("updating candidateboard with {}", newInfo);
348 + updated.setTrue();
349 + return newInfo;
350 + }
351 + return current;
352 + });
353 + // maybe rethink types of candidates events
354 + if (updated.booleanValue()) {
355 + LeadershipEvent event = new LeadershipEvent(LeadershipEvent.Type.CANDIDATES_CHANGED, newInfo);
356 + notifyPeers(event);
357 + }
358 + }
359 +
360 + private void notifyCandidateRemoved(
361 + String path, List<NodeId> candidates, long epoch, long electedTime) {
362 + Leadership newInfo = new Leadership(path, candidates, epoch, electedTime);
363 + final MutableBoolean updated = new MutableBoolean(false);
364 + candidateBoard.compute(path, (k, current) -> {
365 + if (current != null && current.epoch() == newInfo.epoch()) {
366 + log.info("updating candidateboard with {}", newInfo);
367 + updated.setTrue();
368 + if (candidates.isEmpty()) {
369 + return null;
370 + } else {
371 + return newInfo;
372 + }
373 + }
374 + return current;
375 + });
376 + // maybe rethink types of candidates events
377 + if (updated.booleanValue()) {
378 + LeadershipEvent event = new LeadershipEvent(LeadershipEvent.Type.CANDIDATES_CHANGED, newInfo);
379 + notifyPeers(event);
380 + }
381 + }
382 +
324 private void notifyNewLeader(String path, NodeId leader, 383 private void notifyNewLeader(String path, NodeId leader,
325 List<NodeId> candidates, long epoch, long electedTime) { 384 List<NodeId> candidates, long epoch, long electedTime) {
326 Leadership newLeadership = new Leadership(path, leader, candidates, epoch, electedTime); 385 Leadership newLeadership = new Leadership(path, leader, candidates, epoch, electedTime);
327 - boolean updatedLeader = false; 386 + final MutableBoolean updatedLeader = new MutableBoolean(false);
328 log.debug("candidates for new Leadership {}", candidates); 387 log.debug("candidates for new Leadership {}", candidates);
329 - synchronized (leaderBoard) { 388 + leaderBoard.compute(path, (k, currentLeader) -> {
330 - Leadership currentLeader = leaderBoard.get(path);
331 if (currentLeader == null || currentLeader.epoch() < epoch) { 389 if (currentLeader == null || currentLeader.epoch() < epoch) {
332 log.debug("updating leaderboard with new {}", newLeadership); 390 log.debug("updating leaderboard with new {}", newLeadership);
333 - leaderBoard.put(path, newLeadership); 391 + updatedLeader.setTrue();
334 - updatedLeader = true; 392 + return newLeadership;
335 } 393 }
336 - } 394 + return currentLeader;
395 + });
337 396
338 - if (updatedLeader) { 397 + if (updatedLeader.booleanValue()) {
339 LeadershipEvent event = new LeadershipEvent(LeadershipEvent.Type.LEADER_ELECTED, newLeadership); 398 LeadershipEvent event = new LeadershipEvent(LeadershipEvent.Type.LEADER_ELECTED, newLeadership);
340 notifyPeers(event); 399 notifyPeers(event);
341 } 400 }
...@@ -352,21 +411,18 @@ public class DistributedLeadershipManager implements LeadershipService { ...@@ -352,21 +411,18 @@ public class DistributedLeadershipManager implements LeadershipService {
352 Versioned<List<NodeId>> candidates = candidateMap.get(path); 411 Versioned<List<NodeId>> candidates = candidateMap.get(path);
353 Leadership oldLeadership = new Leadership( 412 Leadership oldLeadership = new Leadership(
354 path, leader, candidates.value(), epoch, electedTime); 413 path, leader, candidates.value(), epoch, electedTime);
355 - boolean updatedLeader = false; 414 + final MutableBoolean updatedLeader = new MutableBoolean(false);
356 - synchronized (leaderBoard) { 415 + leaderBoard.compute(path, (k, currentLeader) -> {
357 - Leadership currentLeader = leaderBoard.get(path);
358 if (currentLeader != null && currentLeader.epoch() == oldLeadership.epoch()) { 416 if (currentLeader != null && currentLeader.epoch() == oldLeadership.epoch()) {
359 - leaderBoard.remove(path); 417 + updatedLeader.setTrue();
360 - updatedLeader = true; 418 + return null;
361 } 419 }
362 - } 420 + return currentLeader;
421 + });
363 422
364 - if (updatedLeader) { 423 + if (updatedLeader.booleanValue()) {
365 LeadershipEvent event = new LeadershipEvent(LeadershipEvent.Type.LEADER_BOOTED, oldLeadership); 424 LeadershipEvent event = new LeadershipEvent(LeadershipEvent.Type.LEADER_BOOTED, oldLeadership);
366 - eventDispatcher.post(event); 425 + notifyPeers(event);
367 - clusterCommunicator.broadcast(event,
368 - LEADERSHIP_EVENT_MESSAGE_SUBJECT,
369 - SERIALIZER::encode);
370 } 426 }
371 } 427 }
372 428
...@@ -385,31 +441,37 @@ public class DistributedLeadershipManager implements LeadershipService { ...@@ -385,31 +441,37 @@ public class DistributedLeadershipManager implements LeadershipService {
385 LeadershipEvent.Type eventType = leadershipEvent.type(); 441 LeadershipEvent.Type eventType = leadershipEvent.type();
386 String topic = leadershipUpdate.topic(); 442 String topic = leadershipUpdate.topic();
387 443
388 - boolean updateAccepted = false; 444 + MutableBoolean updateAccepted = new MutableBoolean(false);
389 - 445 + if (eventType.equals(LeadershipEvent.Type.LEADER_ELECTED)) {
390 - synchronized (leaderBoard) { 446 + leaderBoard.compute(topic, (k, currentLeadership) -> {
391 - Leadership currentLeadership = leaderBoard.get(topic);
392 - if (eventType.equals(LeadershipEvent.Type.LEADER_ELECTED)) {
393 if (currentLeadership == null || currentLeadership.epoch() < leadershipUpdate.epoch()) { 447 if (currentLeadership == null || currentLeadership.epoch() < leadershipUpdate.epoch()) {
394 - leaderBoard.put(topic, leadershipUpdate); 448 + updateAccepted.setTrue();
395 - updateAccepted = true; 449 + return leadershipUpdate;
396 } 450 }
397 - } else if (eventType.equals(LeadershipEvent.Type.LEADER_BOOTED)) { 451 + return currentLeadership;
398 - if (currentLeadership != null && currentLeadership.epoch() == leadershipUpdate.epoch()) { 452 + });
399 - leaderBoard.remove(topic); 453 + } else if (eventType.equals(LeadershipEvent.Type.LEADER_BOOTED)) {
400 - updateAccepted = true; 454 + leaderBoard.compute(topic, (k, currentLeadership) -> {
455 + if (currentLeadership == null || currentLeadership.epoch() < leadershipUpdate.epoch()) {
456 + updateAccepted.setTrue();
457 + return null;
401 } 458 }
402 - } else if (eventType.equals(LeadershipEvent.Type.CANDIDATES_CHANGED)) { 459 + return currentLeadership;
403 - if (currentLeadership != null && currentLeadership.epoch() == leadershipUpdate.epoch()) { 460 + });
404 - leaderBoard.replace(topic, leadershipUpdate); 461 + } else if (eventType.equals(LeadershipEvent.Type.CANDIDATES_CHANGED)) {
405 - updateAccepted = true; 462 + candidateBoard.compute(topic, (k, currentInfo) -> {
463 + if (currentInfo == null || currentInfo.epoch() <= leadershipUpdate.epoch()) {
464 + updateAccepted.setTrue();
465 + return leadershipUpdate;
406 } 466 }
407 - } else { 467 + return currentInfo;
408 - throw new IllegalStateException("Unknown event type."); 468 + });
409 - } 469 + } else {
410 - if (updateAccepted) { 470 + throw new IllegalStateException("Unknown event type.");
411 - eventDispatcher.post(leadershipEvent); 471 + }
412 - } 472 +
473 + if (updateAccepted.booleanValue()) {
474 + eventDispatcher.post(leadershipEvent);
413 } 475 }
414 } 476 }
415 } 477 }
...@@ -470,6 +532,12 @@ public class DistributedLeadershipManager implements LeadershipService { ...@@ -470,6 +532,12 @@ public class DistributedLeadershipManager implements LeadershipService {
470 SERIALIZER::encode); 532 SERIALIZER::encode);
471 } 533 }
472 }); 534 });
535 + candidateBoard.forEach((path, leadership) -> {
536 + LeadershipEvent event = new LeadershipEvent(LeadershipEvent.Type.CANDIDATES_CHANGED, leadership);
537 + clusterCommunicator.broadcast(event,
538 + LEADERSHIP_EVENT_MESSAGE_SUBJECT,
539 + SERIALIZER::encode);
540 + });
473 } catch (Exception e) { 541 } catch (Exception e) {
474 log.debug("Failed to send leadership updates", e); 542 log.debug("Failed to send leadership updates", e);
475 } 543 }
......
...@@ -111,7 +111,7 @@ public class SimpleLeadershipManager implements LeadershipService { ...@@ -111,7 +111,7 @@ public class SimpleLeadershipManager implements LeadershipService {
111 } 111 }
112 112
113 @Override 113 @Override
114 - public Map<String, List<NodeId>> getCandidates() { 114 + public Map<String, Leadership> getCandidates() {
115 return null; 115 return null;
116 } 116 }
117 117
......