blob: 9178e900987f2efc14a042ca6c68c06a9a59632b [file] [log] [blame]
Ayaka Koshibe8583ff32014-10-02 16:25:30 -07001package org.onlab.onos.store.cluster.impl;
2
3import static org.junit.Assert.assertEquals;
4import static org.junit.Assert.assertNull;
5import static org.junit.Assert.assertTrue;
6import static org.onlab.onos.net.MastershipRole.*;
7
Ayaka Koshibec4047702014-10-07 14:43:52 -07008import java.util.Map;
Ayaka Koshibe8583ff32014-10-02 16:25:30 -07009import java.util.Set;
Ayaka Koshibe5c0f2372014-10-02 17:59:04 -070010import java.util.concurrent.CountDownLatch;
11import java.util.concurrent.TimeUnit;
Ayaka Koshibe8583ff32014-10-02 16:25:30 -070012
13import org.junit.After;
14import org.junit.AfterClass;
15import org.junit.Before;
16import org.junit.BeforeClass;
Ayaka Koshibe5c0f2372014-10-02 17:59:04 -070017import org.junit.Ignore;
Ayaka Koshibe8583ff32014-10-02 16:25:30 -070018import org.junit.Test;
19import org.onlab.onos.cluster.ClusterEventListener;
20import org.onlab.onos.cluster.ClusterService;
21import org.onlab.onos.cluster.ControllerNode;
22import org.onlab.onos.cluster.ControllerNode.State;
23import org.onlab.onos.cluster.DefaultControllerNode;
Ayaka Koshibe5c0f2372014-10-02 17:59:04 -070024import org.onlab.onos.cluster.MastershipEvent;
Ayaka Koshibe8583ff32014-10-02 16:25:30 -070025import org.onlab.onos.cluster.MastershipEvent.Type;
Ayaka Koshibe5c0f2372014-10-02 17:59:04 -070026import org.onlab.onos.cluster.MastershipStoreDelegate;
Ayaka Koshibe8583ff32014-10-02 16:25:30 -070027import org.onlab.onos.cluster.MastershipTerm;
28import org.onlab.onos.cluster.NodeId;
29import org.onlab.onos.net.DeviceId;
30import org.onlab.onos.store.common.StoreManager;
31import org.onlab.onos.store.common.StoreService;
32import org.onlab.onos.store.common.TestStoreManager;
33import org.onlab.onos.store.serializers.KryoSerializationManager;
34import org.onlab.onos.store.serializers.KryoSerializationService;
35import org.onlab.packet.IpPrefix;
36
37import com.google.common.collect.Sets;
38import com.hazelcast.config.Config;
39import com.hazelcast.core.Hazelcast;
40
41/**
42 * Test of the Hazelcast-based distributed MastershipStore implementation.
43 */
44public class DistributedMastershipStoreTest {
45
46 private static final DeviceId DID1 = DeviceId.deviceId("of:01");
47 private static final DeviceId DID2 = DeviceId.deviceId("of:02");
48 private static final DeviceId DID3 = DeviceId.deviceId("of:03");
Ayaka Koshibe8583ff32014-10-02 16:25:30 -070049
50 private static final IpPrefix IP = IpPrefix.valueOf("127.0.0.1");
51
52 private static final NodeId N1 = new NodeId("node1");
53 private static final NodeId N2 = new NodeId("node2");
54
55 private static final ControllerNode CN1 = new DefaultControllerNode(N1, IP);
56 private static final ControllerNode CN2 = new DefaultControllerNode(N2, IP);
57
58 private DistributedMastershipStore dms;
59 private TestDistributedMastershipStore testStore;
60 private KryoSerializationManager serializationMgr;
61 private StoreManager storeMgr;
62
63 @BeforeClass
64 public static void setUpBeforeClass() throws Exception {
65 }
66
67 @AfterClass
68 public static void tearDownAfterClass() throws Exception {
69 }
70
71 @Before
72 public void setUp() throws Exception {
73 // TODO should find a way to clean Hazelcast instance without shutdown.
74 Config config = TestStoreManager.getTestConfig();
75
76 storeMgr = new TestStoreManager(Hazelcast.newHazelcastInstance(config));
77 storeMgr.activate();
78
79 serializationMgr = new KryoSerializationManager();
80 serializationMgr.activate();
81
82 dms = new TestDistributedMastershipStore(storeMgr, serializationMgr);
83 dms.clusterService = new TestClusterService();
84 dms.activate();
85
86 testStore = (TestDistributedMastershipStore) dms;
87 }
88
89 @After
90 public void tearDown() throws Exception {
91 dms.deactivate();
92
93 serializationMgr.deactivate();
94
95 storeMgr.deactivate();
96 }
97
98 @Test
99 public void getRole() {
100 assertEquals("wrong role:", NONE, dms.getRole(N1, DID1));
Ayaka Koshibec4047702014-10-07 14:43:52 -0700101 testStore.put(DID1, N1, true, false, true);
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700102 assertEquals("wrong role:", MASTER, dms.getRole(N1, DID1));
103 assertEquals("wrong role:", STANDBY, dms.getRole(N2, DID1));
104 }
105
106 @Test
107 public void getMaster() {
Ayaka Koshibec4047702014-10-07 14:43:52 -0700108 assertTrue("wrong store state:", dms.masters.isEmpty());
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700109
110 testStore.put(DID1, N1, true, false, false);
111 assertEquals("wrong master:", N1, dms.getMaster(DID1));
112 assertNull("wrong master:", dms.getMaster(DID2));
113 }
114
115 @Test
116 public void getDevices() {
Ayaka Koshibec4047702014-10-07 14:43:52 -0700117 assertTrue("wrong store state:", dms.masters.isEmpty());
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700118
119 testStore.put(DID1, N1, true, false, false);
120 testStore.put(DID2, N1, true, false, false);
121 testStore.put(DID3, N2, true, false, false);
122
123 assertEquals("wrong devices",
124 Sets.newHashSet(DID1, DID2), dms.getDevices(N1));
125 }
126
127 @Test
128 public void requestRoleAndTerm() {
129 //CN1 is "local"
130 testStore.setCurrent(CN1);
131
132 //if already MASTER, nothing should happen
133 testStore.put(DID2, N1, true, false, false);
134 assertEquals("wrong role for MASTER:", MASTER, dms.requestRole(DID2));
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700135
136 //populate maps with DID1, N1 thru NONE case
137 assertEquals("wrong role for NONE:", MASTER, dms.requestRole(DID1));
Ayaka Koshibec4047702014-10-07 14:43:52 -0700138 assertTrue("wrong state for store:", !dms.terms.isEmpty());
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700139 assertEquals("wrong term",
140 MastershipTerm.of(N1, 0), dms.getTermFor(DID1));
141
142 //CN2 now local. DID2 has N1 as MASTER so N2 is STANDBY
143 testStore.setCurrent(CN2);
144 assertEquals("wrong role for STANDBY:", STANDBY, dms.requestRole(DID2));
Ayaka Koshibec4047702014-10-07 14:43:52 -0700145 assertEquals("wrong number of entries:", 2, dms.terms.size());
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700146
147 //change term and requestRole() again; should persist
148 testStore.increment(DID2);
149 assertEquals("wrong role for STANDBY:", STANDBY, dms.requestRole(DID2));
150 assertEquals("wrong term", MastershipTerm.of(N1, 1), dms.getTermFor(DID2));
151 }
152
153 @Test
154 public void setMaster() {
155 //populate maps with DID1, N1 as MASTER thru NONE case
156 testStore.setCurrent(CN1);
157 assertEquals("wrong role for NONE:", MASTER, dms.requestRole(DID1));
158 assertNull("wrong event:", dms.setMaster(N1, DID1));
159
160 //switch over to N2
161 assertEquals("wrong event:", Type.MASTER_CHANGED, dms.setMaster(N2, DID1).type());
162 assertEquals("wrong term", MastershipTerm.of(N2, 1), dms.getTermFor(DID1));
163
164 //orphan switch - should be rare case
165 assertEquals("wrong event:", Type.MASTER_CHANGED, dms.setMaster(N2, DID2).type());
166 assertEquals("wrong term", MastershipTerm.of(N2, 0), dms.getTermFor(DID2));
167 //disconnect and reconnect - sign of failing re-election or single-instance channel
168 testStore.reset(true, false, false);
169 dms.setMaster(N2, DID2);
170 assertEquals("wrong term", MastershipTerm.of(N2, 1), dms.getTermFor(DID2));
171 }
172
173 @Test
Ayaka Koshibec4047702014-10-07 14:43:52 -0700174 public void relinquishRole() {
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700175 //populate maps with DID1, N1 as MASTER thru NONE case
176 testStore.setCurrent(CN1);
177 assertEquals("wrong role for NONE:", MASTER, dms.requestRole(DID1));
178 //no backup, no new MASTER/event
Ayaka Koshibec4047702014-10-07 14:43:52 -0700179 assertNull("wrong event:", dms.relinquishRole(N1, DID1));
Ayaka Koshibe25fd23a2014-10-03 15:50:43 -0700180
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700181 dms.requestRole(DID1);
Ayaka Koshibe25fd23a2014-10-03 15:50:43 -0700182
183 //add backup CN2, get it elected MASTER by relinquishing
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700184 testStore.setCurrent(CN2);
Ayaka Koshibec4047702014-10-07 14:43:52 -0700185 assertEquals("wrong role for NONE:", STANDBY, dms.requestRole(DID1));
186 assertEquals("wrong event:", Type.MASTER_CHANGED, dms.relinquishRole(N1, DID1).type());
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700187 assertEquals("wrong master", N2, dms.getMaster(DID1));
188
189 //STANDBY - nothing here, either
Ayaka Koshibec4047702014-10-07 14:43:52 -0700190 assertNull("wrong event:", dms.relinquishRole(N1, DID1));
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700191 assertEquals("wrong role for node:", STANDBY, dms.getRole(N1, DID1));
192
Ayaka Koshibec4047702014-10-07 14:43:52 -0700193 //all nodes "give up" on device, which goes back to NONE.
194 assertNull("wrong event:", dms.relinquishRole(N2, DID1));
195 assertEquals("wrong role for node:", NONE, dms.getRole(N2, DID1));
196 assertEquals("wrong role for node:", NONE, dms.getRole(N1, DID1));
Ayaka Koshibe25fd23a2014-10-03 15:50:43 -0700197
Ayaka Koshibec4047702014-10-07 14:43:52 -0700198 assertEquals("wrong number of retired nodes", 2, dms.unusable.size());
199
200 //bring nodes back
201 assertEquals("wrong role for NONE:", MASTER, dms.requestRole(DID1));
202 testStore.setCurrent(CN1);
203 assertEquals("wrong role for NONE:", STANDBY, dms.requestRole(DID1));
204 assertEquals("wrong number of backup nodes", 1, dms.standbys.size());
205
206 //NONE - nothing happens
207 assertNull("wrong event:", dms.relinquishRole(N1, DID2));
208 assertEquals("wrong role for node:", NONE, dms.getRole(N1, DID2));
Ayaka Koshibe25fd23a2014-10-03 15:50:43 -0700209
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700210 }
211
Ayaka Koshibe5c0f2372014-10-02 17:59:04 -0700212 @Ignore("Ignore until Delegate spec. is clear.")
213 @Test
214 public void testEvents() throws InterruptedException {
215 //shamelessly copy other distributed store tests
216 final CountDownLatch addLatch = new CountDownLatch(1);
217
218 MastershipStoreDelegate checkAdd = new MastershipStoreDelegate() {
219 @Override
220 public void notify(MastershipEvent event) {
221 assertEquals("wrong event:", Type.MASTER_CHANGED, event.type());
222 assertEquals("wrong subject", DID1, event.subject());
223 assertEquals("wrong subject", N1, event.master());
224 addLatch.countDown();
225 }
226 };
227
228 dms.setDelegate(checkAdd);
229 dms.setMaster(N1, DID1);
230 //this will fail until we do something about single-instance-ness
231 assertTrue("Add event fired", addLatch.await(1, TimeUnit.SECONDS));
232 }
233
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700234 private class TestDistributedMastershipStore extends
235 DistributedMastershipStore {
236 public TestDistributedMastershipStore(StoreService storeService,
237 KryoSerializationService kryoSerializationService) {
238 this.storeService = storeService;
239 this.kryoSerializationService = kryoSerializationService;
240 }
241
242 //helper to populate master/backup structures
243 public void put(DeviceId dev, NodeId node,
Ayaka Koshibec4047702014-10-07 14:43:52 -0700244 boolean master, boolean backup, boolean term) {
245 byte [] n = serialize(node);
246 byte [] d = serialize(dev);
247
248 if (master) {
249 dms.masters.put(d, n);
250 dms.unusable.put(d, n);
251 dms.standbys.remove(d, n);
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700252 }
253 if (backup) {
Ayaka Koshibec4047702014-10-07 14:43:52 -0700254 dms.standbys.put(d, n);
255 dms.masters.remove(d, n);
256 dms.unusable.remove(d, n);
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700257 }
258 if (term) {
Ayaka Koshibec4047702014-10-07 14:43:52 -0700259 dms.terms.put(d, 0);
260 }
261 }
262
263 public void dump() {
264 System.out.println("standbys");
265 for (Map.Entry<byte [], byte []> e : standbys.entrySet()) {
266 System.out.println(deserialize(e.getKey()) + ":" + deserialize(e.getValue()));
267 }
268 System.out.println("unusable");
269 for (Map.Entry<byte [], byte []> e : unusable.entrySet()) {
270 System.out.println(deserialize(e.getKey()) + ":" + deserialize(e.getValue()));
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700271 }
272 }
273
274 //clears structures
275 public void reset(boolean store, boolean backup, boolean term) {
276 if (store) {
Ayaka Koshibec4047702014-10-07 14:43:52 -0700277 dms.masters.clear();
278 dms.unusable.clear();
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700279 }
280 if (backup) {
Ayaka Koshibec4047702014-10-07 14:43:52 -0700281 dms.standbys.clear();
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700282 }
283 if (term) {
Ayaka Koshibec4047702014-10-07 14:43:52 -0700284 dms.terms.clear();
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700285 }
286 }
287
288 //increment term for a device
289 public void increment(DeviceId dev) {
Ayaka Koshibec4047702014-10-07 14:43:52 -0700290 Integer t = dms.terms.get(serialize(dev));
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700291 if (t != null) {
Ayaka Koshibec4047702014-10-07 14:43:52 -0700292 dms.terms.put(serialize(dev), ++t);
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700293 }
294 }
295
296 //sets the "local" node
297 public void setCurrent(ControllerNode node) {
298 ((TestClusterService) clusterService).current = node;
299 }
300 }
301
302 private class TestClusterService implements ClusterService {
303
304 protected ControllerNode current;
305
306 @Override
307 public ControllerNode getLocalNode() {
308 return current;
309 }
310
311 @Override
312 public Set<ControllerNode> getNodes() {
313 return Sets.newHashSet(CN1, CN2);
314 }
315
316 @Override
317 public ControllerNode getNode(NodeId nodeId) {
318 return null;
319 }
320
321 @Override
322 public State getState(NodeId nodeId) {
323 return null;
324 }
325
326 @Override
327 public void addListener(ClusterEventListener listener) {
328 }
329
330 @Override
331 public void removeListener(ClusterEventListener listener) {
332 }
333
334 }
Ayaka Koshibe25fd23a2014-10-03 15:50:43 -0700335
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700336}