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
Showing
8 changed files
with
145 additions
and
36 deletions
... | @@ -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. Use with great caution since | 268 | * Copies the specified directory path. Use with great caution since | ... | ... |
-
Please register or login to post a comment