tests for DistributedMastershipStore
Change-Id: Ic7daa333ac7d7947155b745daf08e4771f1189ef
Showing
3 changed files
with
440 additions
and
37 deletions
... | @@ -200,7 +200,7 @@ public class DeviceManager | ... | @@ -200,7 +200,7 @@ public class DeviceManager |
200 | // process. | 200 | // process. |
201 | if (event != null) { | 201 | if (event != null) { |
202 | log.info("Device {} connected", deviceId); | 202 | log.info("Device {} connected", deviceId); |
203 | - mastershipService.requestRoleFor(deviceId); | 203 | + //mastershipService.requestRoleFor(deviceId); |
204 | provider().roleChanged(event.subject(), | 204 | provider().roleChanged(event.subject(), |
205 | mastershipService.requestRoleFor(deviceId)); | 205 | mastershipService.requestRoleFor(deviceId)); |
206 | post(event); | 206 | post(event); | ... | ... |
... | @@ -4,7 +4,6 @@ import static com.google.common.cache.CacheBuilder.newBuilder; | ... | @@ -4,7 +4,6 @@ import static com.google.common.cache.CacheBuilder.newBuilder; |
4 | import static org.onlab.onos.cluster.MastershipEvent.Type.MASTER_CHANGED; | 4 | import static org.onlab.onos.cluster.MastershipEvent.Type.MASTER_CHANGED; |
5 | 5 | ||
6 | import java.util.Map; | 6 | import java.util.Map; |
7 | -import java.util.Objects; | ||
8 | import java.util.Set; | 7 | import java.util.Set; |
9 | 8 | ||
10 | import org.apache.felix.scr.annotations.Activate; | 9 | import org.apache.felix.scr.annotations.Activate; |
... | @@ -28,6 +27,7 @@ import org.onlab.onos.store.common.OptionalCacheLoader; | ... | @@ -28,6 +27,7 @@ import org.onlab.onos.store.common.OptionalCacheLoader; |
28 | import com.google.common.base.Optional; | 27 | import com.google.common.base.Optional; |
29 | import com.google.common.cache.LoadingCache; | 28 | import com.google.common.cache.LoadingCache; |
30 | import com.google.common.collect.ImmutableSet; | 29 | import com.google.common.collect.ImmutableSet; |
30 | +import com.hazelcast.core.ILock; | ||
31 | import com.hazelcast.core.IMap; | 31 | import com.hazelcast.core.IMap; |
32 | 32 | ||
33 | /** | 33 | /** |
... | @@ -39,8 +39,22 @@ public class DistributedMastershipStore | ... | @@ -39,8 +39,22 @@ public class DistributedMastershipStore |
39 | extends AbstractHazelcastStore<MastershipEvent, MastershipStoreDelegate> | 39 | extends AbstractHazelcastStore<MastershipEvent, MastershipStoreDelegate> |
40 | implements MastershipStore { | 40 | implements MastershipStore { |
41 | 41 | ||
42 | - private IMap<byte[], byte[]> rawMasters; | 42 | + //arbitrary lock name |
43 | - private LoadingCache<DeviceId, Optional<NodeId>> masters; | 43 | + private static final String LOCK = "lock"; |
44 | + //initial term value | ||
45 | + private static final Integer INIT = 0; | ||
46 | + //placeholder non-null value | ||
47 | + private static final Byte NIL = 0x0; | ||
48 | + | ||
49 | + //devices to masters | ||
50 | + protected IMap<byte[], byte[]> rawMasters; | ||
51 | + //devices to terms | ||
52 | + protected IMap<byte[], Integer> rawTerms; | ||
53 | + //collection of nodes. values are ignored, as it's used as a makeshift 'set' | ||
54 | + protected IMap<byte[], Byte> backups; | ||
55 | + | ||
56 | + //TODO - remove | ||
57 | + //private LoadingCache<DeviceId, Optional<NodeId>> masters; | ||
44 | 58 | ||
45 | @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) | 59 | @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
46 | protected ClusterService clusterService; | 60 | protected ClusterService clusterService; |
... | @@ -51,23 +65,18 @@ implements MastershipStore { | ... | @@ -51,23 +65,18 @@ implements MastershipStore { |
51 | super.activate(); | 65 | super.activate(); |
52 | 66 | ||
53 | rawMasters = theInstance.getMap("masters"); | 67 | rawMasters = theInstance.getMap("masters"); |
54 | - OptionalCacheLoader<DeviceId, NodeId> nodeLoader | 68 | + rawTerms = theInstance.getMap("terms"); |
55 | - = new OptionalCacheLoader<>(kryoSerializationService, rawMasters); | 69 | + backups = theInstance.getMap("backups"); |
56 | - masters = new AbsentInvalidatingLoadingCache<>(newBuilder().build(nodeLoader)); | ||
57 | - rawMasters.addEntryListener(new RemoteMasterShipEventHandler(masters), true); | ||
58 | 70 | ||
59 | - loadMasters(); | 71 | + //TODO: hook up maps to event notification |
72 | + //OptionalCacheLoader<DeviceId, NodeId> nodeLoader | ||
73 | + //= new OptionalCacheLoader<>(kryoSerializationService, rawMasters); | ||
74 | + //masters = new AbsentInvalidatingLoadingCache<>(newBuilder().build(nodeLoader)); | ||
75 | + //rawMasters.addEntryListener(new RemoteMasterShipEventHandler(masters), true); | ||
60 | 76 | ||
61 | log.info("Started"); | 77 | log.info("Started"); |
62 | } | 78 | } |
63 | 79 | ||
64 | - private void loadMasters() { | ||
65 | - for (byte[] keyBytes : rawMasters.keySet()) { | ||
66 | - final DeviceId id = deserialize(keyBytes); | ||
67 | - masters.refresh(id); | ||
68 | - } | ||
69 | - } | ||
70 | - | ||
71 | @Deactivate | 80 | @Deactivate |
72 | public void deactivate() { | 81 | public void deactivate() { |
73 | log.info("Stopped"); | 82 | log.info("Stopped"); |
... | @@ -75,60 +84,178 @@ implements MastershipStore { | ... | @@ -75,60 +84,178 @@ implements MastershipStore { |
75 | 84 | ||
76 | @Override | 85 | @Override |
77 | public MastershipEvent setMaster(NodeId nodeId, DeviceId deviceId) { | 86 | public MastershipEvent setMaster(NodeId nodeId, DeviceId deviceId) { |
78 | - synchronized (this) { | 87 | + byte [] did = serialize(deviceId); |
79 | - NodeId currentMaster = getMaster(deviceId); | 88 | + byte [] nid = serialize(nodeId); |
80 | - if (Objects.equals(currentMaster, nodeId)) { | ||
81 | - return null; | ||
82 | - } | ||
83 | 89 | ||
84 | - // FIXME: for now implementing semantics of setMaster | 90 | + ILock lock = theInstance.getLock(LOCK); |
85 | - rawMasters.put(serialize(deviceId), serialize(nodeId)); | 91 | + lock.lock(); |
86 | - masters.put(deviceId, Optional.of(nodeId)); | 92 | + try { |
87 | - return new MastershipEvent(MastershipEvent.Type.MASTER_CHANGED, deviceId, nodeId); | 93 | + MastershipRole role = getRole(nodeId, deviceId); |
94 | + Integer term = rawTerms.get(did); | ||
95 | + switch (role) { | ||
96 | + case MASTER: | ||
97 | + return null; | ||
98 | + case STANDBY: | ||
99 | + rawMasters.put(did, nid); | ||
100 | + rawTerms.put(did, ++term); | ||
101 | + backups.putIfAbsent(nid, NIL); | ||
102 | + break; | ||
103 | + case NONE: | ||
104 | + rawMasters.put(did, nid); | ||
105 | + //new switch OR state transition after being orphaned | ||
106 | + if (term == null) { | ||
107 | + rawTerms.put(did, INIT); | ||
108 | + } else { | ||
109 | + rawTerms.put(did, ++term); | ||
110 | + } | ||
111 | + backups.put(nid, NIL); | ||
112 | + break; | ||
113 | + default: | ||
114 | + log.warn("unknown Mastership Role {}", role); | ||
115 | + return null; | ||
116 | + } | ||
117 | + return new MastershipEvent(MASTER_CHANGED, deviceId, nodeId); | ||
118 | + } finally { | ||
119 | + lock.unlock(); | ||
88 | } | 120 | } |
89 | } | 121 | } |
90 | 122 | ||
91 | @Override | 123 | @Override |
92 | public NodeId getMaster(DeviceId deviceId) { | 124 | public NodeId getMaster(DeviceId deviceId) { |
93 | - return masters.getUnchecked(deviceId).orNull(); | 125 | + return deserialize(rawMasters.get(serialize(deviceId))); |
94 | } | 126 | } |
95 | 127 | ||
96 | @Override | 128 | @Override |
97 | public Set<DeviceId> getDevices(NodeId nodeId) { | 129 | public Set<DeviceId> getDevices(NodeId nodeId) { |
98 | ImmutableSet.Builder<DeviceId> builder = ImmutableSet.builder(); | 130 | ImmutableSet.Builder<DeviceId> builder = ImmutableSet.builder(); |
99 | - for (Map.Entry<DeviceId, Optional<NodeId>> entry : masters.asMap().entrySet()) { | 131 | + |
100 | - if (nodeId.equals(entry.getValue().get())) { | 132 | + for (Map.Entry<byte[], byte[]> entry : rawMasters.entrySet()) { |
101 | - builder.add(entry.getKey()); | 133 | + if (nodeId.equals(deserialize(entry.getValue()))) { |
134 | + builder.add((DeviceId) deserialize(entry.getKey())); | ||
102 | } | 135 | } |
103 | } | 136 | } |
137 | + | ||
104 | return builder.build(); | 138 | return builder.build(); |
105 | } | 139 | } |
106 | 140 | ||
107 | @Override | 141 | @Override |
108 | public MastershipRole requestRole(DeviceId deviceId) { | 142 | public MastershipRole requestRole(DeviceId deviceId) { |
109 | - // FIXME: for now we are 'selecting' as master whoever asks | 143 | + // first to empty slot for device in master map is MASTER |
110 | - setMaster(clusterService.getLocalNode().id(), deviceId); | 144 | + // depending on how backups are organized, might need to trigger election |
111 | - return MastershipRole.MASTER; | 145 | + // so only controller doesn't set itself to backup for another device |
146 | + byte [] did = serialize(deviceId); | ||
147 | + NodeId local = clusterService.getLocalNode().id(); | ||
148 | + byte [] lnid = serialize(local); | ||
149 | + | ||
150 | + ILock lock = theInstance.getLock(LOCK); | ||
151 | + lock.lock(); | ||
152 | + try { | ||
153 | + MastershipRole role = getRole(local, deviceId); | ||
154 | + switch (role) { | ||
155 | + case MASTER: | ||
156 | + break; | ||
157 | + case STANDBY: | ||
158 | + backups.put(lnid, NIL); | ||
159 | + rawTerms.putIfAbsent(did, INIT); | ||
160 | + break; | ||
161 | + case NONE: | ||
162 | + rawMasters.put(did, lnid); | ||
163 | + rawTerms.putIfAbsent(did, INIT); | ||
164 | + backups.put(lnid, NIL); | ||
165 | + role = MastershipRole.MASTER; | ||
166 | + break; | ||
167 | + default: | ||
168 | + log.warn("unknown Mastership Role {}", role); | ||
169 | + } | ||
170 | + return role; | ||
171 | + } finally { | ||
172 | + lock.unlock(); | ||
173 | + } | ||
112 | } | 174 | } |
113 | 175 | ||
114 | @Override | 176 | @Override |
115 | public MastershipRole getRole(NodeId nodeId, DeviceId deviceId) { | 177 | public MastershipRole getRole(NodeId nodeId, DeviceId deviceId) { |
116 | - NodeId master = masters.getUnchecked(deviceId).orNull(); | 178 | + byte[] did = serialize(deviceId); |
117 | - return nodeId.equals(master) ? MastershipRole.MASTER : MastershipRole.STANDBY; | 179 | + |
180 | + NodeId current = deserialize(rawMasters.get(did)); | ||
181 | + MastershipRole role = null; | ||
182 | + | ||
183 | + if (current == null) { | ||
184 | + //IFF no controllers have claimed mastership over it | ||
185 | + role = MastershipRole.NONE; | ||
186 | + } else { | ||
187 | + if (current.equals(nodeId)) { | ||
188 | + role = MastershipRole.MASTER; | ||
189 | + } else { | ||
190 | + role = MastershipRole.STANDBY; | ||
191 | + } | ||
192 | + } | ||
193 | + | ||
194 | + return role; | ||
118 | } | 195 | } |
119 | 196 | ||
120 | @Override | 197 | @Override |
121 | public MastershipTerm getTermFor(DeviceId deviceId) { | 198 | public MastershipTerm getTermFor(DeviceId deviceId) { |
122 | - // TODO Auto-generated method stub | 199 | + byte[] did = serialize(deviceId); |
123 | - return null; | 200 | + |
201 | + if ((rawMasters.get(did) == null) || | ||
202 | + (rawTerms.get(did) == null)) { | ||
203 | + return null; | ||
204 | + } | ||
205 | + return MastershipTerm.of( | ||
206 | + (NodeId) deserialize(rawMasters.get(did)), rawTerms.get(did)); | ||
124 | } | 207 | } |
125 | 208 | ||
126 | @Override | 209 | @Override |
127 | public MastershipEvent unsetMaster(NodeId nodeId, DeviceId deviceId) { | 210 | public MastershipEvent unsetMaster(NodeId nodeId, DeviceId deviceId) { |
128 | - // TODO Auto-generated method stub | 211 | + byte [] did = serialize(deviceId); |
212 | + | ||
213 | + ILock lock = theInstance.getLock(LOCK); | ||
214 | + lock.lock(); | ||
215 | + try { | ||
216 | + MastershipRole role = getRole(nodeId, deviceId); | ||
217 | + switch (role) { | ||
218 | + case MASTER: | ||
219 | + //hand off device to another | ||
220 | + NodeId backup = reelect(nodeId, deviceId); | ||
221 | + if (backup == null) { | ||
222 | + //goes back to NONE | ||
223 | + rawMasters.remove(did); | ||
224 | + } else { | ||
225 | + //goes to STANDBY for local, MASTER for someone else | ||
226 | + Integer term = rawTerms.get(did); | ||
227 | + rawMasters.put(did, serialize(backup)); | ||
228 | + rawTerms.put(did, ++term); | ||
229 | + return new MastershipEvent(MASTER_CHANGED, deviceId, backup); | ||
230 | + } | ||
231 | + case STANDBY: | ||
232 | + case NONE: | ||
233 | + break; | ||
234 | + default: | ||
235 | + log.warn("unknown Mastership Role {}", role); | ||
236 | + } | ||
237 | + return null; | ||
238 | + } finally { | ||
239 | + lock.unlock(); | ||
240 | + } | ||
241 | + } | ||
242 | + | ||
243 | + //helper for "re-electing" a new master for a given device | ||
244 | + private NodeId reelect(NodeId current, DeviceId deviceId) { | ||
245 | + for (byte [] node : backups.keySet()) { | ||
246 | + NodeId nid = deserialize(node); | ||
247 | + if (!current.equals(nid)) { | ||
248 | + return nid; | ||
249 | + } | ||
250 | + } | ||
129 | return null; | 251 | return null; |
130 | } | 252 | } |
131 | 253 | ||
254 | + //adds node to pool(s) of backup | ||
255 | + private void backup(NodeId nodeId, DeviceId deviceId) { | ||
256 | + //TODO might be useful to isolate out | ||
257 | + } | ||
258 | + | ||
132 | private class RemoteMasterShipEventHandler extends RemoteCacheEventHandler<DeviceId, NodeId> { | 259 | private class RemoteMasterShipEventHandler extends RemoteCacheEventHandler<DeviceId, NodeId> { |
133 | public RemoteMasterShipEventHandler(LoadingCache<DeviceId, Optional<NodeId>> cache) { | 260 | public RemoteMasterShipEventHandler(LoadingCache<DeviceId, Optional<NodeId>> cache) { |
134 | super(cache); | 261 | super(cache); | ... | ... |
1 | +package org.onlab.onos.store.cluster.impl; | ||
2 | + | ||
3 | +import static org.junit.Assert.assertEquals; | ||
4 | +import static org.junit.Assert.assertNull; | ||
5 | +import static org.junit.Assert.assertTrue; | ||
6 | +import static org.onlab.onos.net.MastershipRole.*; | ||
7 | + | ||
8 | +import java.util.Set; | ||
9 | + | ||
10 | +import org.junit.After; | ||
11 | +import org.junit.AfterClass; | ||
12 | +import org.junit.Before; | ||
13 | +import org.junit.BeforeClass; | ||
14 | +import org.junit.Test; | ||
15 | +import org.onlab.onos.cluster.ClusterEventListener; | ||
16 | +import org.onlab.onos.cluster.ClusterService; | ||
17 | +import org.onlab.onos.cluster.ControllerNode; | ||
18 | +import org.onlab.onos.cluster.ControllerNode.State; | ||
19 | +import org.onlab.onos.cluster.DefaultControllerNode; | ||
20 | +import org.onlab.onos.cluster.MastershipEvent.Type; | ||
21 | +import org.onlab.onos.cluster.MastershipTerm; | ||
22 | +import org.onlab.onos.cluster.NodeId; | ||
23 | +import org.onlab.onos.net.DeviceId; | ||
24 | +import org.onlab.onos.store.common.StoreManager; | ||
25 | +import org.onlab.onos.store.common.StoreService; | ||
26 | +import org.onlab.onos.store.common.TestStoreManager; | ||
27 | +import org.onlab.onos.store.serializers.KryoSerializationManager; | ||
28 | +import org.onlab.onos.store.serializers.KryoSerializationService; | ||
29 | +import org.onlab.packet.IpPrefix; | ||
30 | + | ||
31 | +import com.google.common.collect.Sets; | ||
32 | +import com.hazelcast.config.Config; | ||
33 | +import com.hazelcast.core.Hazelcast; | ||
34 | + | ||
35 | +/** | ||
36 | + * Test of the Hazelcast-based distributed MastershipStore implementation. | ||
37 | + */ | ||
38 | +public class DistributedMastershipStoreTest { | ||
39 | + | ||
40 | + private static final DeviceId DID1 = DeviceId.deviceId("of:01"); | ||
41 | + private static final DeviceId DID2 = DeviceId.deviceId("of:02"); | ||
42 | + private static final DeviceId DID3 = DeviceId.deviceId("of:03"); | ||
43 | + private static final DeviceId DID4 = DeviceId.deviceId("of:04"); | ||
44 | + | ||
45 | + private static final IpPrefix IP = IpPrefix.valueOf("127.0.0.1"); | ||
46 | + | ||
47 | + private static final NodeId N1 = new NodeId("node1"); | ||
48 | + private static final NodeId N2 = new NodeId("node2"); | ||
49 | + | ||
50 | + private static final ControllerNode CN1 = new DefaultControllerNode(N1, IP); | ||
51 | + private static final ControllerNode CN2 = new DefaultControllerNode(N2, IP); | ||
52 | + | ||
53 | + private DistributedMastershipStore dms; | ||
54 | + private TestDistributedMastershipStore testStore; | ||
55 | + private KryoSerializationManager serializationMgr; | ||
56 | + private StoreManager storeMgr; | ||
57 | + | ||
58 | + @BeforeClass | ||
59 | + public static void setUpBeforeClass() throws Exception { | ||
60 | + } | ||
61 | + | ||
62 | + @AfterClass | ||
63 | + public static void tearDownAfterClass() throws Exception { | ||
64 | + } | ||
65 | + | ||
66 | + @Before | ||
67 | + public void setUp() throws Exception { | ||
68 | + // TODO should find a way to clean Hazelcast instance without shutdown. | ||
69 | + Config config = TestStoreManager.getTestConfig(); | ||
70 | + | ||
71 | + storeMgr = new TestStoreManager(Hazelcast.newHazelcastInstance(config)); | ||
72 | + storeMgr.activate(); | ||
73 | + | ||
74 | + serializationMgr = new KryoSerializationManager(); | ||
75 | + serializationMgr.activate(); | ||
76 | + | ||
77 | + dms = new TestDistributedMastershipStore(storeMgr, serializationMgr); | ||
78 | + dms.clusterService = new TestClusterService(); | ||
79 | + dms.activate(); | ||
80 | + | ||
81 | + testStore = (TestDistributedMastershipStore) dms; | ||
82 | + } | ||
83 | + | ||
84 | + @After | ||
85 | + public void tearDown() throws Exception { | ||
86 | + dms.deactivate(); | ||
87 | + | ||
88 | + serializationMgr.deactivate(); | ||
89 | + | ||
90 | + storeMgr.deactivate(); | ||
91 | + } | ||
92 | + | ||
93 | + @Test | ||
94 | + public void getRole() { | ||
95 | + assertEquals("wrong role:", NONE, dms.getRole(N1, DID1)); | ||
96 | + testStore.put(DID1, N1, true, true, true); | ||
97 | + assertEquals("wrong role:", MASTER, dms.getRole(N1, DID1)); | ||
98 | + assertEquals("wrong role:", STANDBY, dms.getRole(N2, DID1)); | ||
99 | + } | ||
100 | + | ||
101 | + @Test | ||
102 | + public void getMaster() { | ||
103 | + assertTrue("wrong store state:", dms.rawMasters.isEmpty()); | ||
104 | + | ||
105 | + testStore.put(DID1, N1, true, false, false); | ||
106 | + assertEquals("wrong master:", N1, dms.getMaster(DID1)); | ||
107 | + assertNull("wrong master:", dms.getMaster(DID2)); | ||
108 | + } | ||
109 | + | ||
110 | + @Test | ||
111 | + public void getDevices() { | ||
112 | + assertTrue("wrong store state:", dms.rawMasters.isEmpty()); | ||
113 | + | ||
114 | + testStore.put(DID1, N1, true, false, false); | ||
115 | + testStore.put(DID2, N1, true, false, false); | ||
116 | + testStore.put(DID3, N2, true, false, false); | ||
117 | + | ||
118 | + assertEquals("wrong devices", | ||
119 | + Sets.newHashSet(DID1, DID2), dms.getDevices(N1)); | ||
120 | + } | ||
121 | + | ||
122 | + @Test | ||
123 | + public void requestRoleAndTerm() { | ||
124 | + //CN1 is "local" | ||
125 | + testStore.setCurrent(CN1); | ||
126 | + | ||
127 | + //if already MASTER, nothing should happen | ||
128 | + testStore.put(DID2, N1, true, false, false); | ||
129 | + assertEquals("wrong role for MASTER:", MASTER, dms.requestRole(DID2)); | ||
130 | + assertTrue("wrong state for store:", | ||
131 | + dms.backups.isEmpty() & dms.rawTerms.isEmpty()); | ||
132 | + | ||
133 | + //populate maps with DID1, N1 thru NONE case | ||
134 | + assertEquals("wrong role for NONE:", MASTER, dms.requestRole(DID1)); | ||
135 | + assertTrue("wrong state for store:", | ||
136 | + !dms.backups.isEmpty() & !dms.rawTerms.isEmpty()); | ||
137 | + assertEquals("wrong term", | ||
138 | + MastershipTerm.of(N1, 0), dms.getTermFor(DID1)); | ||
139 | + | ||
140 | + //CN2 now local. DID2 has N1 as MASTER so N2 is STANDBY | ||
141 | + testStore.setCurrent(CN2); | ||
142 | + assertEquals("wrong role for STANDBY:", STANDBY, dms.requestRole(DID2)); | ||
143 | + assertEquals("wrong number of entries:", 2, dms.rawTerms.size()); | ||
144 | + | ||
145 | + //change term and requestRole() again; should persist | ||
146 | + testStore.increment(DID2); | ||
147 | + assertEquals("wrong role for STANDBY:", STANDBY, dms.requestRole(DID2)); | ||
148 | + assertEquals("wrong term", MastershipTerm.of(N1, 1), dms.getTermFor(DID2)); | ||
149 | + } | ||
150 | + | ||
151 | + @Test | ||
152 | + public void setMaster() { | ||
153 | + //populate maps with DID1, N1 as MASTER thru NONE case | ||
154 | + testStore.setCurrent(CN1); | ||
155 | + assertEquals("wrong role for NONE:", MASTER, dms.requestRole(DID1)); | ||
156 | + assertNull("wrong event:", dms.setMaster(N1, DID1)); | ||
157 | + | ||
158 | + //switch over to N2 | ||
159 | + assertEquals("wrong event:", Type.MASTER_CHANGED, dms.setMaster(N2, DID1).type()); | ||
160 | + assertEquals("wrong term", MastershipTerm.of(N2, 1), dms.getTermFor(DID1)); | ||
161 | + | ||
162 | + //orphan switch - should be rare case | ||
163 | + assertEquals("wrong event:", Type.MASTER_CHANGED, dms.setMaster(N2, DID2).type()); | ||
164 | + assertEquals("wrong term", MastershipTerm.of(N2, 0), dms.getTermFor(DID2)); | ||
165 | + //disconnect and reconnect - sign of failing re-election or single-instance channel | ||
166 | + testStore.reset(true, false, false); | ||
167 | + dms.setMaster(N2, DID2); | ||
168 | + assertEquals("wrong term", MastershipTerm.of(N2, 1), dms.getTermFor(DID2)); | ||
169 | + } | ||
170 | + | ||
171 | + @Test | ||
172 | + public void unsetMaster() { | ||
173 | + //populate maps with DID1, N1 as MASTER thru NONE case | ||
174 | + testStore.setCurrent(CN1); | ||
175 | + assertEquals("wrong role for NONE:", MASTER, dms.requestRole(DID1)); | ||
176 | + //no backup, no new MASTER/event | ||
177 | + assertNull("wrong event:", dms.unsetMaster(N1, DID1)); | ||
178 | + //add backup CN2, get it elected MASTER | ||
179 | + dms.requestRole(DID1); | ||
180 | + testStore.setCurrent(CN2); | ||
181 | + dms.requestRole(DID1); | ||
182 | + assertEquals("wrong event:", Type.MASTER_CHANGED, dms.unsetMaster(N1, DID1).type()); | ||
183 | + assertEquals("wrong master", N2, dms.getMaster(DID1)); | ||
184 | + | ||
185 | + //STANDBY - nothing here, either | ||
186 | + assertNull("wrong event:", dms.unsetMaster(N1, DID1)); | ||
187 | + assertEquals("wrong role for node:", STANDBY, dms.getRole(N1, DID1)); | ||
188 | + | ||
189 | + //NONE - nothing happens | ||
190 | + assertNull("wrong event:", dms.unsetMaster(N1, DID2)); | ||
191 | + assertEquals("wrong role for node:", NONE, dms.getRole(N1, DID2)); | ||
192 | + } | ||
193 | + | ||
194 | + private class TestDistributedMastershipStore extends | ||
195 | + DistributedMastershipStore { | ||
196 | + public TestDistributedMastershipStore(StoreService storeService, | ||
197 | + KryoSerializationService kryoSerializationService) { | ||
198 | + this.storeService = storeService; | ||
199 | + this.kryoSerializationService = kryoSerializationService; | ||
200 | + } | ||
201 | + | ||
202 | + //helper to populate master/backup structures | ||
203 | + public void put(DeviceId dev, NodeId node, | ||
204 | + boolean store, boolean backup, boolean term) { | ||
205 | + if (store) { | ||
206 | + dms.rawMasters.put(serialize(dev), serialize(node)); | ||
207 | + } | ||
208 | + if (backup) { | ||
209 | + dms.backups.put(serialize(node), (byte) 0); | ||
210 | + } | ||
211 | + if (term) { | ||
212 | + dms.rawTerms.put(serialize(dev), 0); | ||
213 | + } | ||
214 | + } | ||
215 | + | ||
216 | + //clears structures | ||
217 | + public void reset(boolean store, boolean backup, boolean term) { | ||
218 | + if (store) { | ||
219 | + dms.rawMasters.clear(); | ||
220 | + } | ||
221 | + if (backup) { | ||
222 | + dms.backups.clear(); | ||
223 | + } | ||
224 | + if (term) { | ||
225 | + dms.rawTerms.clear(); | ||
226 | + } | ||
227 | + } | ||
228 | + | ||
229 | + //increment term for a device | ||
230 | + public void increment(DeviceId dev) { | ||
231 | + Integer t = dms.rawTerms.get(serialize(dev)); | ||
232 | + if (t != null) { | ||
233 | + dms.rawTerms.put(serialize(dev), ++t); | ||
234 | + } | ||
235 | + } | ||
236 | + | ||
237 | + //sets the "local" node | ||
238 | + public void setCurrent(ControllerNode node) { | ||
239 | + ((TestClusterService) clusterService).current = node; | ||
240 | + } | ||
241 | + } | ||
242 | + | ||
243 | + private class TestClusterService implements ClusterService { | ||
244 | + | ||
245 | + protected ControllerNode current; | ||
246 | + | ||
247 | + @Override | ||
248 | + public ControllerNode getLocalNode() { | ||
249 | + return current; | ||
250 | + } | ||
251 | + | ||
252 | + @Override | ||
253 | + public Set<ControllerNode> getNodes() { | ||
254 | + return Sets.newHashSet(CN1, CN2); | ||
255 | + } | ||
256 | + | ||
257 | + @Override | ||
258 | + public ControllerNode getNode(NodeId nodeId) { | ||
259 | + return null; | ||
260 | + } | ||
261 | + | ||
262 | + @Override | ||
263 | + public State getState(NodeId nodeId) { | ||
264 | + return null; | ||
265 | + } | ||
266 | + | ||
267 | + @Override | ||
268 | + public void addListener(ClusterEventListener listener) { | ||
269 | + } | ||
270 | + | ||
271 | + @Override | ||
272 | + public void removeListener(ClusterEventListener listener) { | ||
273 | + } | ||
274 | + | ||
275 | + } | ||
276 | +} |
-
Please register or login to post a comment