Yuta HIGUCHI

DistributedMastershipStore

- try to avoid unnecessary remote writes
- avoid reads from blocking if possible
- atomically read term info

Change-Id: I50badc718726261ccb14a6feefc578b420d28923
...@@ -18,6 +18,7 @@ package org.onlab.onos.store.mastership.impl; ...@@ -18,6 +18,7 @@ package org.onlab.onos.store.mastership.impl;
18 import static org.onlab.onos.mastership.MastershipEvent.Type.MASTER_CHANGED; 18 import static org.onlab.onos.mastership.MastershipEvent.Type.MASTER_CHANGED;
19 import static org.apache.commons.lang3.concurrent.ConcurrentUtils.putIfAbsent; 19 import static org.apache.commons.lang3.concurrent.ConcurrentUtils.putIfAbsent;
20 20
21 +import java.util.HashSet;
21 import java.util.Map; 22 import java.util.Map;
22 import java.util.Set; 23 import java.util.Set;
23 24
...@@ -42,7 +43,6 @@ import org.onlab.onos.store.serializers.KryoNamespaces; ...@@ -42,7 +43,6 @@ import org.onlab.onos.store.serializers.KryoNamespaces;
42 import org.onlab.onos.store.serializers.KryoSerializer; 43 import org.onlab.onos.store.serializers.KryoSerializer;
43 import org.onlab.util.KryoNamespace; 44 import org.onlab.util.KryoNamespace;
44 45
45 -import com.google.common.collect.ImmutableSet;
46 import com.hazelcast.core.EntryEvent; 46 import com.hazelcast.core.EntryEvent;
47 import com.hazelcast.core.EntryListener; 47 import com.hazelcast.core.EntryListener;
48 import com.hazelcast.core.IAtomicLong; 48 import com.hazelcast.core.IAtomicLong;
...@@ -106,46 +106,50 @@ implements MastershipStore { ...@@ -106,46 +106,50 @@ implements MastershipStore {
106 106
107 @Override 107 @Override
108 public MastershipRole getRole(NodeId nodeId, DeviceId deviceId) { 108 public MastershipRole getRole(NodeId nodeId, DeviceId deviceId) {
109 - final RoleValue roleInfo = getRoleValue(deviceId); 109 + final RoleValue roleInfo = roleMap.get(deviceId);
110 - if (roleInfo.contains(MASTER, nodeId)) { 110 + if (roleInfo != null) {
111 - return MASTER; 111 + return roleInfo.getRole(nodeId);
112 - }
113 - if (roleInfo.contains(STANDBY, nodeId)) {
114 - return STANDBY;
115 } 112 }
116 return NONE; 113 return NONE;
117 } 114 }
118 115
119 @Override 116 @Override
120 - public MastershipEvent setMaster(NodeId nodeId, DeviceId deviceId) { 117 + public MastershipEvent setMaster(NodeId newMaster, DeviceId deviceId) {
121 118
122 - MastershipRole role = getRole(nodeId, deviceId);
123 roleMap.lock(deviceId); 119 roleMap.lock(deviceId);
124 try { 120 try {
125 - RoleValue rv = getRoleValue(deviceId); 121 + final RoleValue rv = getRoleValue(deviceId);
126 - switch (role) { 122 + final MastershipRole currentRole = rv.getRole(newMaster);
123 + switch (currentRole) {
127 case MASTER: 124 case MASTER:
128 //reinforce mastership 125 //reinforce mastership
129 - rv.reassign(nodeId, STANDBY, NONE); 126 + // RoleInfo integrity check
130 - roleMap.put(deviceId, rv); 127 + boolean modified = rv.reassign(newMaster, STANDBY, NONE);
128 + if (modified) {
129 + roleMap.put(deviceId, rv);
130 + // should never reach here.
131 + log.warn("{} was in both MASTER and STANDBY for {}", newMaster, deviceId);
132 + // trigger BACKUPS_CHANGED?
133 + }
131 return null; 134 return null;
132 case STANDBY: 135 case STANDBY:
133 case NONE: 136 case NONE:
134 - NodeId current = rv.get(MASTER); 137 + final NodeId currentMaster = rv.get(MASTER);
135 - if (current != null) { 138 + if (currentMaster != null) {
136 - //backup and replace current master 139 + // place current master in STANDBY
137 - rv.reassign(current, NONE, STANDBY); 140 + rv.reassign(currentMaster, NONE, STANDBY);
138 - rv.replace(current, nodeId, MASTER); 141 + rv.replace(currentMaster, newMaster, MASTER);
139 } else { 142 } else {
140 //no master before so just add. 143 //no master before so just add.
141 - rv.add(MASTER, nodeId); 144 + rv.add(MASTER, newMaster);
142 } 145 }
143 - rv.reassign(nodeId, STANDBY, NONE); 146 + // remove newMaster from STANDBY
144 - roleMap.put(deviceId, rv); 147 + rv.reassign(newMaster, STANDBY, NONE);
145 updateTerm(deviceId); 148 updateTerm(deviceId);
149 + roleMap.put(deviceId, rv);
146 return new MastershipEvent(MASTER_CHANGED, deviceId, rv.roleInfo()); 150 return new MastershipEvent(MASTER_CHANGED, deviceId, rv.roleInfo());
147 default: 151 default:
148 - log.warn("unknown Mastership Role {}", role); 152 + log.warn("unknown Mastership Role {}", currentRole);
149 return null; 153 return null;
150 } 154 }
151 } finally { 155 } finally {
...@@ -161,66 +165,83 @@ implements MastershipStore { ...@@ -161,66 +165,83 @@ implements MastershipStore {
161 165
162 @Override 166 @Override
163 public RoleInfo getNodes(DeviceId deviceId) { 167 public RoleInfo getNodes(DeviceId deviceId) {
164 - roleMap.lock(deviceId); 168 + RoleValue rv = roleMap.get(deviceId);
165 - try { 169 + if (rv != null) {
166 - RoleValue rv = getRoleValue(deviceId);
167 return rv.roleInfo(); 170 return rv.roleInfo();
168 - } finally { 171 + } else {
169 - roleMap.unlock(deviceId); 172 + return new RoleInfo();
170 } 173 }
171 } 174 }
172 175
173 @Override 176 @Override
174 public Set<DeviceId> getDevices(NodeId nodeId) { 177 public Set<DeviceId> getDevices(NodeId nodeId) {
175 - ImmutableSet.Builder<DeviceId> builder = ImmutableSet.builder(); 178 + Set<DeviceId> devices = new HashSet<>();
176 179
177 for (Map.Entry<DeviceId, RoleValue> el : roleMap.entrySet()) { 180 for (Map.Entry<DeviceId, RoleValue> el : roleMap.entrySet()) {
178 if (nodeId.equals(el.getValue().get(MASTER))) { 181 if (nodeId.equals(el.getValue().get(MASTER))) {
179 - builder.add(el.getKey()); 182 + devices.add(el.getKey());
180 } 183 }
181 } 184 }
182 185
183 - return builder.build(); 186 + return devices;
184 } 187 }
185 188
186 @Override 189 @Override
187 public MastershipRole requestRole(DeviceId deviceId) { 190 public MastershipRole requestRole(DeviceId deviceId) {
188 - NodeId local = clusterService.getLocalNode().id();
189 191
192 + // if no master => become master
193 + // if there already exists a master:
194 + // if I was the master return MASTER
195 + // else put myself in STANDBY and return STANDBY
196 +
197 + final NodeId local = clusterService.getLocalNode().id();
198 + boolean modified = false;
190 roleMap.lock(deviceId); 199 roleMap.lock(deviceId);
191 try { 200 try {
192 - RoleValue rv = getRoleValue(deviceId); 201 + final RoleValue rv = getRoleValue(deviceId);
193 - MastershipRole role = getRole(local, deviceId); 202 + if (rv.get(MASTER) == null) {
194 - switch (role) { 203 + // there's no master become one
204 + // move out from STANDBY
205 + rv.reassign(local, STANDBY, NONE);
206 + rv.add(MASTER, local);
207 +
208 + updateTerm(deviceId);
209 + roleMap.put(deviceId, rv);
210 + return MASTER;
211 + }
212 + final MastershipRole currentRole = rv.getRole(local);
213 + switch (currentRole) {
195 case MASTER: 214 case MASTER:
196 - rv.reassign(local, STANDBY, NONE); 215 + // RoleInfo integrity check
197 - terms.putIfAbsent(deviceId, INIT); 216 + modified = rv.reassign(local, STANDBY, NONE);
198 - roleMap.put(deviceId, rv); 217 + if (modified) {
199 - break; 218 + log.warn("{} was in both MASTER and STANDBY for {}", local, deviceId);
219 + // should never reach here,
220 + // but heal if we happened to be there
221 + roleMap.put(deviceId, rv);
222 + // trigger BACKUPS_CHANGED?
223 + }
224 + return currentRole;
200 case STANDBY: 225 case STANDBY:
201 - rv.reassign(local, NONE, STANDBY); 226 + // RoleInfo integrity check
202 - roleMap.put(deviceId, rv); 227 + modified = rv.reassign(local, NONE, STANDBY);
203 - terms.putIfAbsent(deviceId, INIT); 228 + if (modified) {
204 - break; 229 + log.warn("{} was in both NONE and STANDBY for {}", local, deviceId);
205 - case NONE: 230 + // should never reach here,
206 - //either we're the first standby, or first to device. 231 + // but heal if we happened to be there
207 - //for latter, claim mastership. 232 + roleMap.put(deviceId, rv);
208 - if (rv.get(MASTER) == null) { 233 + // trigger BACKUPS_CHANGED?
209 - rv.add(MASTER, local);
210 - rv.reassign(local, STANDBY, NONE);
211 - updateTerm(deviceId);
212 - role = MastershipRole.MASTER;
213 - } else {
214 - rv.add(STANDBY, local);
215 - rv.reassign(local, NONE, STANDBY);
216 - role = MastershipRole.STANDBY;
217 } 234 }
235 + return currentRole;
236 + case NONE:
237 + rv.reassign(local, NONE, STANDBY);
218 roleMap.put(deviceId, rv); 238 roleMap.put(deviceId, rv);
219 - break; 239 + // TODO: notifyDelegate BACKUPS_CHANGED
240 + return STANDBY;
220 default: 241 default:
221 - log.warn("unknown Mastership Role {}", role); 242 + log.warn("unknown Mastership Role {}", currentRole);
222 } 243 }
223 - return role; 244 + return currentRole;
224 } finally { 245 } finally {
225 roleMap.unlock(deviceId); 246 roleMap.unlock(deviceId);
226 } 247 }
...@@ -228,35 +249,58 @@ implements MastershipStore { ...@@ -228,35 +249,58 @@ implements MastershipStore {
228 249
229 @Override 250 @Override
230 public MastershipTerm getTermFor(DeviceId deviceId) { 251 public MastershipTerm getTermFor(DeviceId deviceId) {
231 - RoleValue rv = getRoleValue(deviceId); 252 + // term information and role must be read atomically
232 - if ((rv.get(MASTER) == null) || (terms.get(deviceId) == null)) { 253 + // acquiring write lock for the device
233 - return null; 254 + roleMap.lock(deviceId);
255 + try {
256 + RoleValue rv = getRoleValue(deviceId);
257 + final Integer term = terms.get(deviceId);
258 + final NodeId master = rv.get(MASTER);
259 + if ((master == null) || (term == null)) {
260 + return null;
261 + }
262 + return MastershipTerm.of(master, term);
263 + } finally {
264 + roleMap.unlock(deviceId);
234 } 265 }
235 - return MastershipTerm.of(rv.get(MASTER), terms.get(deviceId));
236 } 266 }
237 267
238 @Override 268 @Override
239 public MastershipEvent setStandby(NodeId nodeId, DeviceId deviceId) { 269 public MastershipEvent setStandby(NodeId nodeId, DeviceId deviceId) {
240 - MastershipEvent event = null; 270 + // if nodeId was MASTER, rotate STANDBY
271 + // if nodeId was STANDBY no-op
272 + // if nodeId was NONE, add to STANDBY
241 273
242 roleMap.lock(deviceId); 274 roleMap.lock(deviceId);
243 try { 275 try {
244 - RoleValue rv = getRoleValue(deviceId); 276 + final RoleValue rv = getRoleValue(deviceId);
245 - MastershipRole role = getRole(nodeId, deviceId); 277 + final MastershipRole currentRole = getRole(nodeId, deviceId);
246 - switch (role) { 278 + switch (currentRole) {
247 case MASTER: 279 case MASTER:
248 - event = reelect(nodeId, deviceId, rv); 280 + NodeId newMaster = reelect(nodeId, deviceId, rv);
249 - //fall through to reinforce role 281 + rv.reassign(nodeId, NONE, STANDBY);
282 + if (newMaster != null) {
283 + updateTerm(deviceId);
284 + roleMap.put(deviceId, rv);
285 + return new MastershipEvent(MASTER_CHANGED, deviceId, rv.roleInfo());
286 + } else {
287 + // no master candidate
288 + roleMap.put(deviceId, rv);
289 + // FIXME: Should there be new event type?
290 + // or should we issue null Master event?
291 + return null;
292 + }
250 case STANDBY: 293 case STANDBY:
251 - //fall through to reinforce role 294 + return null;
252 case NONE: 295 case NONE:
253 rv.reassign(nodeId, NONE, STANDBY); 296 rv.reassign(nodeId, NONE, STANDBY);
254 roleMap.put(deviceId, rv); 297 roleMap.put(deviceId, rv);
255 - break; 298 + // TODO: BACKUPS_CHANGED?
299 + return null;
256 default: 300 default:
257 - log.warn("unknown Mastership Role {}", role); 301 + log.warn("unknown Mastership Role {}", currentRole);
258 } 302 }
259 - return event; 303 + return null;
260 } finally { 304 } finally {
261 roleMap.unlock(deviceId); 305 roleMap.unlock(deviceId);
262 } 306 }
...@@ -264,56 +308,71 @@ implements MastershipStore { ...@@ -264,56 +308,71 @@ implements MastershipStore {
264 308
265 @Override 309 @Override
266 public MastershipEvent relinquishRole(NodeId nodeId, DeviceId deviceId) { 310 public MastershipEvent relinquishRole(NodeId nodeId, DeviceId deviceId) {
267 - MastershipEvent event = null; 311 + // relinquishRole is basically set to None
312 +
313 + // If nodeId was master reelect next and remove nodeId
314 + // else remove from STANDBY
268 315
269 roleMap.lock(deviceId); 316 roleMap.lock(deviceId);
270 try { 317 try {
271 - RoleValue rv = getRoleValue(deviceId); 318 + final RoleValue rv = getRoleValue(deviceId);
272 - MastershipRole role = getRole(nodeId, deviceId); 319 + final MastershipRole currentRole = rv.getRole(nodeId);
273 - switch (role) { 320 + switch (currentRole) {
274 case MASTER: 321 case MASTER:
275 - event = reelect(nodeId, deviceId, rv); 322 + NodeId newMaster = reelect(nodeId, deviceId, rv);
276 - if (event != null) { 323 + if (newMaster != null) {
277 updateTerm(deviceId); 324 updateTerm(deviceId);
325 + roleMap.put(deviceId, rv);
326 + return new MastershipEvent(MASTER_CHANGED, deviceId, rv.roleInfo());
327 + } else {
328 + // no master candidate
329 + roleMap.put(deviceId, rv);
330 + // Should there be new event type?
331 + return null;
278 } 332 }
279 - //fall through to reinforce relinquishment
280 case STANDBY: 333 case STANDBY:
281 //fall through to reinforce relinquishment 334 //fall through to reinforce relinquishment
282 case NONE: 335 case NONE:
283 - rv.reassign(nodeId, STANDBY, NONE); 336 + boolean modified = rv.reassign(nodeId, STANDBY, NONE);
284 - roleMap.put(deviceId, rv); 337 + if (modified) {
285 - break; 338 + roleMap.put(deviceId, rv);
339 + // TODO: BACKUPS_CHANGED?
340 + return null;
341 + }
342 + return null;
286 default: 343 default:
287 - log.warn("unknown Mastership Role {}", role); 344 + log.warn("unknown Mastership Role {}", currentRole);
288 } 345 }
289 - return event; 346 + return null;
290 } finally { 347 } finally {
291 roleMap.unlock(deviceId); 348 roleMap.unlock(deviceId);
292 } 349 }
293 } 350 }
294 351
352 + // TODO: Consider moving this to RoleValue method
295 //helper to fetch a new master candidate for a given device. 353 //helper to fetch a new master candidate for a given device.
296 - private MastershipEvent reelect( 354 + private NodeId reelect(
297 NodeId current, DeviceId deviceId, RoleValue rv) { 355 NodeId current, DeviceId deviceId, RoleValue rv) {
298 356
299 //if this is an queue it'd be neater. 357 //if this is an queue it'd be neater.
300 - NodeId backup = null; 358 + NodeId candidate = null;
301 for (NodeId n : rv.nodesOfRole(STANDBY)) { 359 for (NodeId n : rv.nodesOfRole(STANDBY)) {
302 if (!current.equals(n)) { 360 if (!current.equals(n)) {
303 - backup = n; 361 + candidate = n;
304 break; 362 break;
305 } 363 }
306 } 364 }
307 365
308 - if (backup == null) { 366 + if (candidate == null) {
309 log.info("{} giving up and going to NONE for {}", current, deviceId); 367 log.info("{} giving up and going to NONE for {}", current, deviceId);
310 rv.remove(MASTER, current); 368 rv.remove(MASTER, current);
369 + // master did change, but there is no master candidate.
311 return null; 370 return null;
312 } else { 371 } else {
313 - log.info("{} trying to pass mastership for {} to {}", current, deviceId, backup); 372 + log.info("{} trying to pass mastership for {} to {}", current, deviceId, candidate);
314 - rv.replace(current, backup, MASTER); 373 + rv.replace(current, candidate, MASTER);
315 - rv.reassign(backup, STANDBY, NONE); 374 + rv.reassign(candidate, STANDBY, NONE);
316 - return new MastershipEvent(MASTER_CHANGED, deviceId, rv.roleInfo()); 375 + return candidate;
317 } 376 }
318 } 377 }
319 378
...@@ -340,6 +399,7 @@ implements MastershipStore { ...@@ -340,6 +399,7 @@ implements MastershipStore {
340 } 399 }
341 400
342 //adds or updates term information. 401 //adds or updates term information.
402 + // must be guarded by roleMap.lock(deviceId)
343 private void updateTerm(DeviceId deviceId) { 403 private void updateTerm(DeviceId deviceId) {
344 Integer term = terms.get(deviceId); 404 Integer term = terms.get(deviceId);
345 if (term == null) { 405 if (term == null) {
......
...@@ -15,6 +15,10 @@ ...@@ -15,6 +15,10 @@
15 */ 15 */
16 package org.onlab.onos.store.mastership.impl; 16 package org.onlab.onos.store.mastership.impl;
17 17
18 +import static org.onlab.onos.net.MastershipRole.MASTER;
19 +import static org.onlab.onos.net.MastershipRole.NONE;
20 +import static org.onlab.onos.net.MastershipRole.STANDBY;
21 +
18 import java.util.Collections; 22 import java.util.Collections;
19 import java.util.EnumMap; 23 import java.util.EnumMap;
20 import java.util.LinkedList; 24 import java.util.LinkedList;
...@@ -59,18 +63,30 @@ final class RoleValue { ...@@ -59,18 +63,30 @@ final class RoleValue {
59 return value.get(type).contains(nodeId); 63 return value.get(type).contains(nodeId);
60 } 64 }
61 65
66 + public MastershipRole getRole(NodeId nodeId) {
67 + if (contains(MASTER, nodeId)) {
68 + return MASTER;
69 + }
70 + if (contains(STANDBY, nodeId)) {
71 + return STANDBY;
72 + }
73 + return NONE;
74 + }
75 +
62 /** 76 /**
63 * Associates a node to a certain role. 77 * Associates a node to a certain role.
64 * 78 *
65 * @param type the role 79 * @param type the role
66 * @param nodeId the node ID of the node to associate 80 * @param nodeId the node ID of the node to associate
81 + * @return true if modified
67 */ 82 */
68 - public void add(MastershipRole type, NodeId nodeId) { 83 + public boolean add(MastershipRole type, NodeId nodeId) {
69 List<NodeId> nodes = value.get(type); 84 List<NodeId> nodes = value.get(type);
70 85
71 if (!nodes.contains(nodeId)) { 86 if (!nodes.contains(nodeId)) {
72 - nodes.add(nodeId); 87 + return nodes.add(nodeId);
73 } 88 }
89 + return false;
74 } 90 }
75 91
76 /** 92 /**
...@@ -78,7 +94,7 @@ final class RoleValue { ...@@ -78,7 +94,7 @@ final class RoleValue {
78 * 94 *
79 * @param type the role 95 * @param type the role
80 * @param nodeId the ID of the node to remove 96 * @param nodeId the ID of the node to remove
81 - * @return 97 + * @return true if modified
82 */ 98 */
83 public boolean remove(MastershipRole type, NodeId nodeId) { 99 public boolean remove(MastershipRole type, NodeId nodeId) {
84 List<NodeId> nodes = value.get(type); 100 List<NodeId> nodes = value.get(type);
...@@ -96,10 +112,12 @@ final class RoleValue { ...@@ -96,10 +112,12 @@ final class RoleValue {
96 * @param nodeId the Node ID of node changing roles 112 * @param nodeId the Node ID of node changing roles
97 * @param from the old role 113 * @param from the old role
98 * @param to the new role 114 * @param to the new role
115 + * @return true if modified
99 */ 116 */
100 - public void reassign(NodeId nodeId, MastershipRole from, MastershipRole to) { 117 + public boolean reassign(NodeId nodeId, MastershipRole from, MastershipRole to) {
101 - remove(from, nodeId); 118 + boolean modified = remove(from, nodeId);
102 - add(to, nodeId); 119 + modified |= add(to, nodeId);
120 + return modified;
103 } 121 }
104 122
105 /** 123 /**
...@@ -109,10 +127,12 @@ final class RoleValue { ...@@ -109,10 +127,12 @@ final class RoleValue {
109 * @param from the old NodeId to replace 127 * @param from the old NodeId to replace
110 * @param to the new NodeId 128 * @param to the new NodeId
111 * @param type the role associated with the old NodeId 129 * @param type the role associated with the old NodeId
130 + * @return true if modified
112 */ 131 */
113 - public void replace(NodeId from, NodeId to, MastershipRole type) { 132 + public boolean replace(NodeId from, NodeId to, MastershipRole type) {
114 - remove(type, from); 133 + boolean modified = remove(type, from);
115 - add(type, to); 134 + modified |= add(type, to);
135 + return modified;
116 } 136 }
117 137
118 /** 138 /**
......
...@@ -142,7 +142,7 @@ public class DistributedMastershipStoreTest { ...@@ -142,7 +142,7 @@ public class DistributedMastershipStoreTest {
142 testStore.setCurrent(CN1); 142 testStore.setCurrent(CN1);
143 143
144 //if already MASTER, nothing should happen 144 //if already MASTER, nothing should happen
145 - testStore.put(DID2, N1, true, false, false); 145 + testStore.put(DID2, N1, true, false, true);
146 assertEquals("wrong role for MASTER:", MASTER, dms.requestRole(DID2)); 146 assertEquals("wrong role for MASTER:", MASTER, dms.requestRole(DID2));
147 147
148 //populate maps with DID1, N1 thru NONE case 148 //populate maps with DID1, N1 thru NONE case
......