Madan Jampani

Added creationTime to Versioned object. This enables supporting a electedTime in…

… leadership, which in turn helps us track how stable leadership terms are.

Change-Id: Ib051027625324646152ed85535ba337e95f8a061
...@@ -19,6 +19,7 @@ import java.util.Comparator; ...@@ -19,6 +19,7 @@ import java.util.Comparator;
19 import java.util.Map; 19 import java.util.Map;
20 20
21 import org.apache.karaf.shell.commands.Command; 21 import org.apache.karaf.shell.commands.Command;
22 +import org.onlab.util.Tools;
22 import org.onosproject.cli.AbstractShellCommand; 23 import org.onosproject.cli.AbstractShellCommand;
23 import org.onosproject.cluster.Leadership; 24 import org.onosproject.cluster.Leadership;
24 import org.onosproject.cluster.LeadershipService; 25 import org.onosproject.cluster.LeadershipService;
...@@ -30,15 +31,16 @@ import org.onosproject.cluster.LeadershipService; ...@@ -30,15 +31,16 @@ import org.onosproject.cluster.LeadershipService;
30 description = "Finds the leader for particular topic.") 31 description = "Finds the leader for particular topic.")
31 public class LeaderCommand extends AbstractShellCommand { 32 public class LeaderCommand extends AbstractShellCommand {
32 33
33 - private static final String FMT = "%-20s | %-15s | %-6s |"; 34 + private static final String FMT = "%-20s | %-15s | %-6s | %-10s |";
34 35
35 @Override 36 @Override
36 protected void execute() { 37 protected void execute() {
37 LeadershipService leaderService = get(LeadershipService.class); 38 LeadershipService leaderService = get(LeadershipService.class);
38 Map<String, Leadership> leaderBoard = leaderService.getLeaderBoard(); 39 Map<String, Leadership> leaderBoard = leaderService.getLeaderBoard();
39 - print("-------------------------------------------------"); 40 + print("--------------------------------------------------------------");
40 - print(FMT, "Topic", "Leader", "Epoch"); 41 + print(FMT, "Topic", "Leader", "Epoch", "Elected");
41 - print("-------------------------------------------------"); 42 + print("--------------------------------------------------------------");
43 +
42 44
43 Comparator<Leadership> leadershipComparator = 45 Comparator<Leadership> leadershipComparator =
44 (e1, e2) -> { 46 (e1, e2) -> {
...@@ -57,8 +59,11 @@ public class LeaderCommand extends AbstractShellCommand { ...@@ -57,8 +59,11 @@ public class LeaderCommand extends AbstractShellCommand {
57 leaderBoard.values() 59 leaderBoard.values()
58 .stream() 60 .stream()
59 .sorted(leadershipComparator) 61 .sorted(leadershipComparator)
60 - .forEach(l -> print(FMT, l.topic(), l.leader(), l.epoch())); 62 + .forEach(l -> print(FMT,
61 - print("-------------------------------------------------"); 63 + l.topic(),
64 + l.leader(),
65 + l.epoch(),
66 + Tools.timeAgo(l.electedTime())));
67 + print("--------------------------------------------------------------");
62 } 68 }
63 -
64 } 69 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -17,6 +17,8 @@ package org.onosproject.cluster; ...@@ -17,6 +17,8 @@ package org.onosproject.cluster;
17 17
18 import java.util.Objects; 18 import java.util.Objects;
19 19
20 +import org.joda.time.DateTime;
21 +
20 import com.google.common.base.MoreObjects; 22 import com.google.common.base.MoreObjects;
21 23
22 /** 24 /**
...@@ -27,11 +29,13 @@ public class Leadership { ...@@ -27,11 +29,13 @@ public class Leadership {
27 private final String topic; 29 private final String topic;
28 private final NodeId leader; 30 private final NodeId leader;
29 private final long epoch; 31 private final long epoch;
32 + private final long electedTime;
30 33
31 - public Leadership(String topic, NodeId leader, long epoch) { 34 + public Leadership(String topic, NodeId leader, long epoch, long electedTime) {
32 this.topic = topic; 35 this.topic = topic;
33 this.leader = leader; 36 this.leader = leader;
34 this.epoch = epoch; 37 this.epoch = epoch;
38 + this.electedTime = electedTime;
35 } 39 }
36 40
37 /** 41 /**
...@@ -52,12 +56,31 @@ public class Leadership { ...@@ -52,12 +56,31 @@ public class Leadership {
52 56
53 /** 57 /**
54 * The epoch when the leadership was assumed. 58 * The epoch when the leadership was assumed.
59 + * <p>
60 + * Comparing epochs is only appropriate for leadership
61 + * events for the same topic. The system guarantees that
62 + * for any given topic the epoch for a new term is higher
63 + * (not necessarily by 1) than the epoch for any previous term.
55 * @return leadership epoch 64 * @return leadership epoch
56 */ 65 */
57 public long epoch() { 66 public long epoch() {
58 return epoch; 67 return epoch;
59 } 68 }
60 69
70 + /**
71 + * The system time when the term started.
72 + * <p>
73 + * The elected time is initially set on the node coordinating
74 + * the leader election using its local system time. Due to possible
75 + * clock skew, relying on this value for determining event ordering
76 + * is discouraged. Epoch is more appropriate for determining
77 + * event ordering.
78 + * @return elected time.
79 + */
80 + public long electedTime() {
81 + return electedTime;
82 + }
83 +
61 @Override 84 @Override
62 public int hashCode() { 85 public int hashCode() {
63 return Objects.hash(topic, leader, epoch); 86 return Objects.hash(topic, leader, epoch);
...@@ -72,7 +95,8 @@ public class Leadership { ...@@ -72,7 +95,8 @@ public class Leadership {
72 final Leadership other = (Leadership) obj; 95 final Leadership other = (Leadership) obj;
73 return Objects.equals(this.topic, other.topic) && 96 return Objects.equals(this.topic, other.topic) &&
74 Objects.equals(this.leader, other.leader) && 97 Objects.equals(this.leader, other.leader) &&
75 - Objects.equals(this.epoch, other.epoch); 98 + Objects.equals(this.epoch, other.epoch) &&
99 + Objects.equals(this.electedTime, other.electedTime);
76 } 100 }
77 return false; 101 return false;
78 } 102 }
...@@ -83,6 +107,7 @@ public class Leadership { ...@@ -83,6 +107,7 @@ public class Leadership {
83 .add("topic", topic) 107 .add("topic", topic)
84 .add("leader", leader) 108 .add("leader", leader)
85 .add("epoch", epoch) 109 .add("epoch", epoch)
110 + .add("electedTime", new DateTime(electedTime))
86 .toString(); 111 .toString();
87 } 112 }
88 } 113 }
......
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
16 16
17 package org.onosproject.store.service; 17 package org.onosproject.store.service;
18 18
19 +import org.joda.time.DateTime;
20 +
19 import com.google.common.base.MoreObjects; 21 import com.google.common.base.MoreObjects;
20 22
21 /** 23 /**
...@@ -27,15 +29,28 @@ public class Versioned<V> { ...@@ -27,15 +29,28 @@ public class Versioned<V> {
27 29
28 private final V value; 30 private final V value;
29 private final long version; 31 private final long version;
32 + private final long creationTime;
30 33
31 /** 34 /**
32 * Constructs a new versioned value. 35 * Constructs a new versioned value.
33 * @param value value 36 * @param value value
34 * @param version version 37 * @param version version
38 + * @param creationTime milliseconds of the creation event
39 + * from the Java epoch of 1970-01-01T00:00:00Z
35 */ 40 */
36 - public Versioned(V value, long version) { 41 + public Versioned(V value, long version, long creationTime) {
37 this.value = value; 42 this.value = value;
38 this.version = version; 43 this.version = version;
44 + this.creationTime = System.currentTimeMillis();
45 + }
46 +
47 + /**
48 + * Constructs a new versioned value.
49 + * @param value value
50 + * @param version version
51 + */
52 + public Versioned(V value, long version) {
53 + this(value, version, System.currentTimeMillis());
39 } 54 }
40 55
41 /** 56 /**
...@@ -56,11 +71,26 @@ public class Versioned<V> { ...@@ -56,11 +71,26 @@ public class Versioned<V> {
56 return version; 71 return version;
57 } 72 }
58 73
74 + /**
75 + * Returns the system time when this version was created.
76 + * <p>
77 + * Care should be taken when relying on creationTime to
78 + * implement any behavior in a distributed setting. Due
79 + * to the possibility of clock skew it is likely that
80 + * even creationTimes of causally related versions can be
81 + * out or order.
82 + * @return creation time
83 + */
84 + public long creationTime() {
85 + return creationTime;
86 + }
87 +
59 @Override 88 @Override
60 public String toString() { 89 public String toString() {
61 return MoreObjects.toStringHelper(this) 90 return MoreObjects.toStringHelper(this)
62 .add("value", value) 91 .add("value", value)
63 .add("version", version) 92 .add("version", version)
93 + .add("creationTime", new DateTime(creationTime))
64 .toString(); 94 .toString();
65 } 95 }
66 } 96 }
......
...@@ -170,7 +170,8 @@ public class HazelcastLeadershipService implements LeadershipService { ...@@ -170,7 +170,8 @@ public class HazelcastLeadershipService implements LeadershipService {
170 if (topic != null) { 170 if (topic != null) {
171 return new Leadership(topic.topicName(), 171 return new Leadership(topic.topicName(),
172 topic.leader(), 172 topic.leader(),
173 - topic.term()); 173 + topic.term(),
174 + 0);
174 } 175 }
175 return null; 176 return null;
176 } 177 }
...@@ -215,7 +216,8 @@ public class HazelcastLeadershipService implements LeadershipService { ...@@ -215,7 +216,8 @@ public class HazelcastLeadershipService implements LeadershipService {
215 for (Topic topic : topics.values()) { 216 for (Topic topic : topics.values()) {
216 Leadership leadership = new Leadership(topic.topicName(), 217 Leadership leadership = new Leadership(topic.topicName(),
217 topic.leader(), 218 topic.leader(),
218 - topic.term()); 219 + topic.term(),
220 + 0);
219 result.put(topic.topicName(), leadership); 221 result.put(topic.topicName(), leadership);
220 } 222 }
221 return result; 223 return result;
...@@ -412,7 +414,7 @@ public class HazelcastLeadershipService implements LeadershipService { ...@@ -412,7 +414,7 @@ public class HazelcastLeadershipService implements LeadershipService {
412 // 414 //
413 leadershipEvent = new LeadershipEvent( 415 leadershipEvent = new LeadershipEvent(
414 LeadershipEvent.Type.LEADER_REELECTED, 416 LeadershipEvent.Type.LEADER_REELECTED,
415 - new Leadership(topicName, localNodeId, myLastLeaderTerm)); 417 + new Leadership(topicName, localNodeId, myLastLeaderTerm, 0));
416 // Dispatch to all instances 418 // Dispatch to all instances
417 419
418 clusterCommunicator.broadcastIncludeSelf( 420 clusterCommunicator.broadcastIncludeSelf(
...@@ -431,7 +433,7 @@ public class HazelcastLeadershipService implements LeadershipService { ...@@ -431,7 +433,7 @@ public class HazelcastLeadershipService implements LeadershipService {
431 topicName, leader); 433 topicName, leader);
432 leadershipEvent = new LeadershipEvent( 434 leadershipEvent = new LeadershipEvent(
433 LeadershipEvent.Type.LEADER_BOOTED, 435 LeadershipEvent.Type.LEADER_BOOTED,
434 - new Leadership(topicName, leader, myLastLeaderTerm)); 436 + new Leadership(topicName, leader, myLastLeaderTerm, 0));
435 // Dispatch only to the local listener(s) 437 // Dispatch only to the local listener(s)
436 eventDispatcher.post(leadershipEvent); 438 eventDispatcher.post(leadershipEvent);
437 leader = null; 439 leader = null;
...@@ -487,7 +489,7 @@ public class HazelcastLeadershipService implements LeadershipService { ...@@ -487,7 +489,7 @@ public class HazelcastLeadershipService implements LeadershipService {
487 leader = localNodeId; 489 leader = localNodeId;
488 leadershipEvent = new LeadershipEvent( 490 leadershipEvent = new LeadershipEvent(
489 LeadershipEvent.Type.LEADER_ELECTED, 491 LeadershipEvent.Type.LEADER_ELECTED,
490 - new Leadership(topicName, localNodeId, myLastLeaderTerm)); 492 + new Leadership(topicName, localNodeId, myLastLeaderTerm, 0));
491 clusterCommunicator.broadcastIncludeSelf( 493 clusterCommunicator.broadcastIncludeSelf(
492 new ClusterMessage( 494 new ClusterMessage(
493 clusterService.getLocalNode().id(), 495 clusterService.getLocalNode().id(),
...@@ -515,7 +517,7 @@ public class HazelcastLeadershipService implements LeadershipService { ...@@ -515,7 +517,7 @@ public class HazelcastLeadershipService implements LeadershipService {
515 } 517 }
516 leadershipEvent = new LeadershipEvent( 518 leadershipEvent = new LeadershipEvent(
517 LeadershipEvent.Type.LEADER_BOOTED, 519 LeadershipEvent.Type.LEADER_BOOTED,
518 - new Leadership(topicName, localNodeId, myLastLeaderTerm)); 520 + new Leadership(topicName, localNodeId, myLastLeaderTerm, 0));
519 clusterCommunicator.broadcastIncludeSelf( 521 clusterCommunicator.broadcastIncludeSelf(
520 new ClusterMessage( 522 new ClusterMessage(
521 clusterService.getLocalNode().id(), 523 clusterService.getLocalNode().id(),
......
...@@ -105,7 +105,13 @@ public class ConsistentMapImpl<K, V> implements ConsistentMap<K, V> { ...@@ -105,7 +105,13 @@ public class ConsistentMapImpl<K, V> implements ConsistentMap<K, V> {
105 public Versioned<V> get(K key) { 105 public Versioned<V> get(K key) {
106 checkNotNull(key, ERROR_NULL_KEY); 106 checkNotNull(key, ERROR_NULL_KEY);
107 Versioned<byte[]> value = complete(proxy.get(name, keyCache.getUnchecked(key))); 107 Versioned<byte[]> value = complete(proxy.get(name, keyCache.getUnchecked(key)));
108 - return (value != null) ? new Versioned<>(serializer.decode(value.value()), value.version()) : null; 108 + if (value == null) {
109 + return null;
110 + }
111 + return new Versioned<>(
112 + serializer.decode(value.value()),
113 + value.version(),
114 + value.creationTime());
109 } 115 }
110 116
111 @Override 117 @Override
...@@ -114,16 +120,26 @@ public class ConsistentMapImpl<K, V> implements ConsistentMap<K, V> { ...@@ -114,16 +120,26 @@ public class ConsistentMapImpl<K, V> implements ConsistentMap<K, V> {
114 checkNotNull(value, ERROR_NULL_VALUE); 120 checkNotNull(value, ERROR_NULL_VALUE);
115 Versioned<byte[]> previousValue = 121 Versioned<byte[]> previousValue =
116 complete(proxy.put(name, keyCache.getUnchecked(key), serializer.encode(value))); 122 complete(proxy.put(name, keyCache.getUnchecked(key), serializer.encode(value)));
117 - return (previousValue != null) ? 123 + if (previousValue == null) {
118 - new Versioned<>(serializer.decode(previousValue.value()), previousValue.version()) : null; 124 + return null;
119 - 125 + }
126 + return new Versioned<>(
127 + serializer.decode(previousValue.value()),
128 + previousValue.version(),
129 + previousValue.creationTime());
120 } 130 }
121 131
122 @Override 132 @Override
123 public Versioned<V> remove(K key) { 133 public Versioned<V> remove(K key) {
124 checkNotNull(key, ERROR_NULL_KEY); 134 checkNotNull(key, ERROR_NULL_KEY);
125 Versioned<byte[]> value = complete(proxy.remove(name, keyCache.getUnchecked(key))); 135 Versioned<byte[]> value = complete(proxy.remove(name, keyCache.getUnchecked(key)));
126 - return (value != null) ? new Versioned<>(serializer.decode(value.value()), value.version()) : null; 136 + if (value == null) {
137 + return null;
138 + }
139 + return new Versioned<>(
140 + serializer.decode(value.value()),
141 + value.version(),
142 + value.creationTime());
127 } 143 }
128 144
129 @Override 145 @Override
...@@ -143,7 +159,7 @@ public class ConsistentMapImpl<K, V> implements ConsistentMap<K, V> { ...@@ -143,7 +159,7 @@ public class ConsistentMapImpl<K, V> implements ConsistentMap<K, V> {
143 public Collection<Versioned<V>> values() { 159 public Collection<Versioned<V>> values() {
144 return Collections.unmodifiableList(complete(proxy.values(name)) 160 return Collections.unmodifiableList(complete(proxy.values(name))
145 .stream() 161 .stream()
146 - .map(v -> new Versioned<V>(serializer.decode(v.value()), v.version())) 162 + .map(v -> new Versioned<V>(serializer.decode(v.value()), v.version(), v.creationTime()))
147 .collect(Collectors.toList())); 163 .collect(Collectors.toList()));
148 } 164 }
149 165
...@@ -161,8 +177,13 @@ public class ConsistentMapImpl<K, V> implements ConsistentMap<K, V> { ...@@ -161,8 +177,13 @@ public class ConsistentMapImpl<K, V> implements ConsistentMap<K, V> {
161 checkNotNull(value, ERROR_NULL_VALUE); 177 checkNotNull(value, ERROR_NULL_VALUE);
162 Versioned<byte[]> existingValue = complete(proxy.putIfAbsent( 178 Versioned<byte[]> existingValue = complete(proxy.putIfAbsent(
163 name, keyCache.getUnchecked(key), serializer.encode(value))); 179 name, keyCache.getUnchecked(key), serializer.encode(value)));
164 - return (existingValue != null) ? 180 + if (existingValue == null) {
165 - new Versioned<>(serializer.decode(existingValue.value()), existingValue.version()) : null; 181 + return null;
182 + }
183 + return new Versioned<>(
184 + serializer.decode(existingValue.value()),
185 + existingValue.version(),
186 + existingValue.creationTime());
166 } 187 }
167 188
168 @Override 189 @Override
...@@ -212,6 +233,7 @@ public class ConsistentMapImpl<K, V> implements ConsistentMap<K, V> { ...@@ -212,6 +233,7 @@ public class ConsistentMapImpl<K, V> implements ConsistentMap<K, V> {
212 dK(e.getKey()), 233 dK(e.getKey()),
213 new Versioned<>( 234 new Versioned<>(
214 serializer.decode(e.getValue().value()), 235 serializer.decode(e.getValue().value()),
215 - e.getValue().version())); 236 + e.getValue().version(),
237 + e.getValue().creationTime()));
216 } 238 }
217 } 239 }
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -227,7 +227,7 @@ public class DistributedLeadershipManager implements LeadershipService { ...@@ -227,7 +227,7 @@ public class DistributedLeadershipManager implements LeadershipService {
227 if (currentLeader != null) { 227 if (currentLeader != null) {
228 if (localNodeId.equals(currentLeader.value())) { 228 if (localNodeId.equals(currentLeader.value())) {
229 log.info("Already has leadership for {}", path); 229 log.info("Already has leadership for {}", path);
230 - notifyNewLeader(path, localNodeId, currentLeader.version()); 230 + notifyNewLeader(path, localNodeId, currentLeader.version(), currentLeader.creationTime());
231 } else { 231 } else {
232 // someone else has leadership. will retry after sometime. 232 // someone else has leadership. will retry after sometime.
233 retry(path); 233 retry(path);
...@@ -237,7 +237,7 @@ public class DistributedLeadershipManager implements LeadershipService { ...@@ -237,7 +237,7 @@ public class DistributedLeadershipManager implements LeadershipService {
237 log.info("Assumed leadership for {}", path); 237 log.info("Assumed leadership for {}", path);
238 // do a get again to get the version (epoch) 238 // do a get again to get the version (epoch)
239 Versioned<NodeId> newLeader = lockMap.get(path); 239 Versioned<NodeId> newLeader = lockMap.get(path);
240 - notifyNewLeader(path, localNodeId, newLeader.version()); 240 + notifyNewLeader(path, localNodeId, newLeader.version(), newLeader.creationTime());
241 } else { 241 } else {
242 // someone beat us to it. 242 // someone beat us to it.
243 retry(path); 243 retry(path);
...@@ -249,8 +249,8 @@ public class DistributedLeadershipManager implements LeadershipService { ...@@ -249,8 +249,8 @@ public class DistributedLeadershipManager implements LeadershipService {
249 } 249 }
250 } 250 }
251 251
252 - private void notifyNewLeader(String path, NodeId leader, long epoch) { 252 + private void notifyNewLeader(String path, NodeId leader, long epoch, long electedTime) {
253 - Leadership newLeadership = new Leadership(path, leader, epoch); 253 + Leadership newLeadership = new Leadership(path, leader, epoch, electedTime);
254 boolean updatedLeader = false; 254 boolean updatedLeader = false;
255 synchronized (leaderBoard) { 255 synchronized (leaderBoard) {
256 Leadership currentLeader = leaderBoard.get(path); 256 Leadership currentLeader = leaderBoard.get(path);
...@@ -271,8 +271,8 @@ public class DistributedLeadershipManager implements LeadershipService { ...@@ -271,8 +271,8 @@ public class DistributedLeadershipManager implements LeadershipService {
271 } 271 }
272 } 272 }
273 273
274 - private void notifyRemovedLeader(String path, NodeId leader, long epoch) { 274 + private void notifyRemovedLeader(String path, NodeId leader, long epoch, long electedTime) {
275 - Leadership oldLeadership = new Leadership(path, leader, epoch); 275 + Leadership oldLeadership = new Leadership(path, leader, epoch, electedTime);
276 boolean updatedLeader = false; 276 boolean updatedLeader = false;
277 synchronized (leaderBoard) { 277 synchronized (leaderBoard) {
278 Leadership currentLeader = leaderBoard.get(path); 278 Leadership currentLeader = leaderBoard.get(path);
...@@ -346,12 +346,13 @@ public class DistributedLeadershipManager implements LeadershipService { ...@@ -346,12 +346,13 @@ public class DistributedLeadershipManager implements LeadershipService {
346 String path = entry.getKey(); 346 String path = entry.getKey();
347 NodeId nodeId = entry.getValue().value(); 347 NodeId nodeId = entry.getValue().value();
348 long epoch = entry.getValue().version(); 348 long epoch = entry.getValue().version();
349 + long creationTime = entry.getValue().creationTime();
349 if (clusterService.getState(nodeId) == ControllerNode.State.INACTIVE) { 350 if (clusterService.getState(nodeId) == ControllerNode.State.INACTIVE) {
350 log.info("Lock for {} is held by {} which is currently inactive", path, nodeId); 351 log.info("Lock for {} is held by {} which is currently inactive", path, nodeId);
351 try { 352 try {
352 if (lockMap.remove(path, epoch)) { 353 if (lockMap.remove(path, epoch)) {
353 log.info("Purged stale lock held by {} for {}", nodeId, path); 354 log.info("Purged stale lock held by {} for {}", nodeId, path);
354 - notifyRemovedLeader(path, nodeId, epoch); 355 + notifyRemovedLeader(path, nodeId, epoch, creationTime);
355 } 356 }
356 } catch (Exception e) { 357 } catch (Exception e) {
357 log.warn("Failed to purge stale lock held by {} for {}", nodeId, path, e); 358 log.warn("Failed to purge stale lock held by {} for {}", nodeId, path, e);
...@@ -362,7 +363,7 @@ public class DistributedLeadershipManager implements LeadershipService { ...@@ -362,7 +363,7 @@ public class DistributedLeadershipManager implements LeadershipService {
362 try { 363 try {
363 if (lockMap.remove(path, epoch)) { 364 if (lockMap.remove(path, epoch)) {
364 log.info("Purged stale lock held by {} for {}", nodeId, path); 365 log.info("Purged stale lock held by {} for {}", nodeId, path);
365 - notifyRemovedLeader(path, nodeId, epoch); 366 + notifyRemovedLeader(path, nodeId, epoch, creationTime);
366 } 367 }
367 } catch (Exception e) { 368 } catch (Exception e) {
368 log.warn("Failed to purge stale lock held by {} for {}", nodeId, path, e); 369 log.warn("Failed to purge stale lock held by {} for {}", nodeId, path, e);
......
...@@ -61,7 +61,7 @@ public class SimpleLeadershipManager implements LeadershipService { ...@@ -61,7 +61,7 @@ public class SimpleLeadershipManager implements LeadershipService {
61 @Override 61 @Override
62 public Leadership getLeadership(String path) { 62 public Leadership getLeadership(String path) {
63 checkArgument(path != null); 63 checkArgument(path != null);
64 - return elections.get(path) ? new Leadership(path, clusterService.getLocalNode().id(), 0) : null; 64 + return elections.get(path) ? new Leadership(path, clusterService.getLocalNode().id(), 0, 0) : null;
65 } 65 }
66 66
67 @Override 67 @Override
...@@ -79,7 +79,7 @@ public class SimpleLeadershipManager implements LeadershipService { ...@@ -79,7 +79,7 @@ public class SimpleLeadershipManager implements LeadershipService {
79 elections.put(path, true); 79 elections.put(path, true);
80 for (LeadershipEventListener listener : listeners) { 80 for (LeadershipEventListener listener : listeners) {
81 listener.event(new LeadershipEvent(Type.LEADER_ELECTED, 81 listener.event(new LeadershipEvent(Type.LEADER_ELECTED,
82 - new Leadership(path, clusterService.getLocalNode().id(), 0))); 82 + new Leadership(path, clusterService.getLocalNode().id(), 0, 0)));
83 } 83 }
84 } 84 }
85 85
...@@ -88,7 +88,7 @@ public class SimpleLeadershipManager implements LeadershipService { ...@@ -88,7 +88,7 @@ public class SimpleLeadershipManager implements LeadershipService {
88 elections.remove(path); 88 elections.remove(path);
89 for (LeadershipEventListener listener : listeners) { 89 for (LeadershipEventListener listener : listeners) {
90 listener.event(new LeadershipEvent(Type.LEADER_BOOTED, 90 listener.event(new LeadershipEvent(Type.LEADER_BOOTED,
91 - new Leadership(path, clusterService.getLocalNode().id(), 0))); 91 + new Leadership(path, clusterService.getLocalNode().id(), 0, 0)));
92 } 92 }
93 } 93 }
94 94
......
...@@ -18,6 +18,7 @@ package org.onlab.util; ...@@ -18,6 +18,7 @@ package org.onlab.util;
18 import com.google.common.base.Strings; 18 import com.google.common.base.Strings;
19 import com.google.common.primitives.UnsignedLongs; 19 import com.google.common.primitives.UnsignedLongs;
20 import com.google.common.util.concurrent.ThreadFactoryBuilder; 20 import com.google.common.util.concurrent.ThreadFactoryBuilder;
21 +
21 import org.slf4j.Logger; 22 import org.slf4j.Logger;
22 23
23 import java.io.BufferedReader; 24 import java.io.BufferedReader;
...@@ -239,6 +240,29 @@ public abstract class Tools { ...@@ -239,6 +240,29 @@ public abstract class Tools {
239 } 240 }
240 } 241 }
241 242
243 + /**
244 + * Returns a human friendly time ago string for a specified system time.
245 + * @param unixTime system time in millis
246 + * @return human friendly time ago
247 + */
248 + public static String timeAgo(long unixTime) {
249 + long deltaMillis = System.currentTimeMillis() - unixTime;
250 + long secondsSince = (long) (deltaMillis / 1000.0);
251 + long minsSince = (long) (deltaMillis / (1000.0 * 60));
252 + long hoursSince = (long) (deltaMillis / (1000.0 * 60 * 60));
253 + long daysSince = (long) (deltaMillis / (1000.0 * 60 * 60 * 24));
254 + if (daysSince > 0) {
255 + return String.format("%dd ago", daysSince);
256 + } else if (hoursSince > 0) {
257 + return String.format("%dh ago", hoursSince);
258 + } else if (minsSince > 0) {
259 + return String.format("%dm ago", minsSince);
260 + } else if (secondsSince > 0) {
261 + return String.format("%ds ago", secondsSince);
262 + } else {
263 + return "just now";
264 + }
265 + }
242 266
243 /** 267 /**
244 * Copies the specified directory path.&nbsp;Use with great caution since 268 * Copies the specified directory path.&nbsp;Use with great caution since
......