blob: bf1bb38e113aaee0f3d29155858167216b6355ef [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;
Ayaka Koshibe4c891272014-10-08 17:14:16 -070033import org.onlab.onos.store.serializers.KryoSerializer;
Ayaka Koshibe8583ff32014-10-02 16:25:30 -070034import org.onlab.packet.IpPrefix;
35
36import com.google.common.collect.Sets;
37import com.hazelcast.config.Config;
38import com.hazelcast.core.Hazelcast;
39
40/**
41 * Test of the Hazelcast-based distributed MastershipStore implementation.
42 */
43public class DistributedMastershipStoreTest {
44
45 private static final DeviceId DID1 = DeviceId.deviceId("of:01");
46 private static final DeviceId DID2 = DeviceId.deviceId("of:02");
47 private static final DeviceId DID3 = DeviceId.deviceId("of:03");
Ayaka Koshibe8583ff32014-10-02 16:25:30 -070048
49 private static final IpPrefix IP = IpPrefix.valueOf("127.0.0.1");
50
51 private static final NodeId N1 = new NodeId("node1");
52 private static final NodeId N2 = new NodeId("node2");
53
54 private static final ControllerNode CN1 = new DefaultControllerNode(N1, IP);
55 private static final ControllerNode CN2 = new DefaultControllerNode(N2, IP);
56
57 private DistributedMastershipStore dms;
58 private TestDistributedMastershipStore testStore;
Ayaka Koshibe4c891272014-10-08 17:14:16 -070059 private KryoSerializer serializationMgr;
Ayaka Koshibe8583ff32014-10-02 16:25:30 -070060 private StoreManager storeMgr;
61
62 @BeforeClass
63 public static void setUpBeforeClass() throws Exception {
64 }
65
66 @AfterClass
67 public static void tearDownAfterClass() throws Exception {
68 }
69
70 @Before
71 public void setUp() throws Exception {
72 // TODO should find a way to clean Hazelcast instance without shutdown.
73 Config config = TestStoreManager.getTestConfig();
74
75 storeMgr = new TestStoreManager(Hazelcast.newHazelcastInstance(config));
76 storeMgr.activate();
77
Ayaka Koshibe4c891272014-10-08 17:14:16 -070078 serializationMgr = new KryoSerializer();
Ayaka Koshibe8583ff32014-10-02 16:25:30 -070079
80 dms = new TestDistributedMastershipStore(storeMgr, serializationMgr);
81 dms.clusterService = new TestClusterService();
82 dms.activate();
83
84 testStore = (TestDistributedMastershipStore) dms;
85 }
86
87 @After
88 public void tearDown() throws Exception {
89 dms.deactivate();
90
Ayaka Koshibe8583ff32014-10-02 16:25:30 -070091 storeMgr.deactivate();
92 }
93
94 @Test
95 public void getRole() {
96 assertEquals("wrong role:", NONE, dms.getRole(N1, DID1));
Ayaka Koshibec4047702014-10-07 14:43:52 -070097 testStore.put(DID1, N1, true, false, true);
Ayaka Koshibe8583ff32014-10-02 16:25:30 -070098 assertEquals("wrong role:", MASTER, dms.getRole(N1, DID1));
99 assertEquals("wrong role:", STANDBY, dms.getRole(N2, DID1));
100 }
101
102 @Test
103 public void getMaster() {
Ayaka Koshibec4047702014-10-07 14:43:52 -0700104 assertTrue("wrong store state:", dms.masters.isEmpty());
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700105
106 testStore.put(DID1, N1, true, false, false);
107 assertEquals("wrong master:", N1, dms.getMaster(DID1));
108 assertNull("wrong master:", dms.getMaster(DID2));
109 }
110
111 @Test
112 public void getDevices() {
Ayaka Koshibec4047702014-10-07 14:43:52 -0700113 assertTrue("wrong store state:", dms.masters.isEmpty());
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700114
115 testStore.put(DID1, N1, true, false, false);
116 testStore.put(DID2, N1, true, false, false);
117 testStore.put(DID3, N2, true, false, false);
118
119 assertEquals("wrong devices",
120 Sets.newHashSet(DID1, DID2), dms.getDevices(N1));
121 }
122
123 @Test
124 public void requestRoleAndTerm() {
125 //CN1 is "local"
126 testStore.setCurrent(CN1);
127
128 //if already MASTER, nothing should happen
129 testStore.put(DID2, N1, true, false, false);
130 assertEquals("wrong role for MASTER:", MASTER, dms.requestRole(DID2));
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700131
132 //populate maps with DID1, N1 thru NONE case
133 assertEquals("wrong role for NONE:", MASTER, dms.requestRole(DID1));
Ayaka Koshibec4047702014-10-07 14:43:52 -0700134 assertTrue("wrong state for store:", !dms.terms.isEmpty());
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700135 assertEquals("wrong term",
136 MastershipTerm.of(N1, 0), dms.getTermFor(DID1));
137
138 //CN2 now local. DID2 has N1 as MASTER so N2 is STANDBY
139 testStore.setCurrent(CN2);
140 assertEquals("wrong role for STANDBY:", STANDBY, dms.requestRole(DID2));
Ayaka Koshibec4047702014-10-07 14:43:52 -0700141 assertEquals("wrong number of entries:", 2, dms.terms.size());
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700142
143 //change term and requestRole() again; should persist
144 testStore.increment(DID2);
145 assertEquals("wrong role for STANDBY:", STANDBY, dms.requestRole(DID2));
146 assertEquals("wrong term", MastershipTerm.of(N1, 1), dms.getTermFor(DID2));
147 }
148
149 @Test
150 public void setMaster() {
151 //populate maps with DID1, N1 as MASTER thru NONE case
152 testStore.setCurrent(CN1);
153 assertEquals("wrong role for NONE:", MASTER, dms.requestRole(DID1));
154 assertNull("wrong event:", dms.setMaster(N1, DID1));
155
156 //switch over to N2
157 assertEquals("wrong event:", Type.MASTER_CHANGED, dms.setMaster(N2, DID1).type());
158 assertEquals("wrong term", MastershipTerm.of(N2, 1), dms.getTermFor(DID1));
159
160 //orphan switch - should be rare case
161 assertEquals("wrong event:", Type.MASTER_CHANGED, dms.setMaster(N2, DID2).type());
162 assertEquals("wrong term", MastershipTerm.of(N2, 0), dms.getTermFor(DID2));
163 //disconnect and reconnect - sign of failing re-election or single-instance channel
164 testStore.reset(true, false, false);
165 dms.setMaster(N2, DID2);
166 assertEquals("wrong term", MastershipTerm.of(N2, 1), dms.getTermFor(DID2));
167 }
168
169 @Test
Ayaka Koshibec4047702014-10-07 14:43:52 -0700170 public void relinquishRole() {
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700171 //populate maps with DID1, N1 as MASTER thru NONE case
172 testStore.setCurrent(CN1);
173 assertEquals("wrong role for NONE:", MASTER, dms.requestRole(DID1));
174 //no backup, no new MASTER/event
Ayaka Koshibec4047702014-10-07 14:43:52 -0700175 assertNull("wrong event:", dms.relinquishRole(N1, DID1));
Ayaka Koshibe25fd23a2014-10-03 15:50:43 -0700176
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700177 dms.requestRole(DID1);
Ayaka Koshibe25fd23a2014-10-03 15:50:43 -0700178
179 //add backup CN2, get it elected MASTER by relinquishing
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700180 testStore.setCurrent(CN2);
Ayaka Koshibec4047702014-10-07 14:43:52 -0700181 assertEquals("wrong role for NONE:", STANDBY, dms.requestRole(DID1));
182 assertEquals("wrong event:", Type.MASTER_CHANGED, dms.relinquishRole(N1, DID1).type());
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700183 assertEquals("wrong master", N2, dms.getMaster(DID1));
184
185 //STANDBY - nothing here, either
Ayaka Koshibec4047702014-10-07 14:43:52 -0700186 assertNull("wrong event:", dms.relinquishRole(N1, DID1));
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700187 assertEquals("wrong role for node:", STANDBY, dms.getRole(N1, DID1));
188
Ayaka Koshibec4047702014-10-07 14:43:52 -0700189 //all nodes "give up" on device, which goes back to NONE.
190 assertNull("wrong event:", dms.relinquishRole(N2, DID1));
191 assertEquals("wrong role for node:", NONE, dms.getRole(N2, DID1));
192 assertEquals("wrong role for node:", NONE, dms.getRole(N1, DID1));
Ayaka Koshibe25fd23a2014-10-03 15:50:43 -0700193
Ayaka Koshibec4047702014-10-07 14:43:52 -0700194 assertEquals("wrong number of retired nodes", 2, dms.unusable.size());
195
196 //bring nodes back
197 assertEquals("wrong role for NONE:", MASTER, dms.requestRole(DID1));
198 testStore.setCurrent(CN1);
199 assertEquals("wrong role for NONE:", STANDBY, dms.requestRole(DID1));
200 assertEquals("wrong number of backup nodes", 1, dms.standbys.size());
201
202 //NONE - nothing happens
203 assertNull("wrong event:", dms.relinquishRole(N1, DID2));
204 assertEquals("wrong role for node:", NONE, dms.getRole(N1, DID2));
Ayaka Koshibe25fd23a2014-10-03 15:50:43 -0700205
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700206 }
207
Ayaka Koshibe5c0f2372014-10-02 17:59:04 -0700208 @Ignore("Ignore until Delegate spec. is clear.")
209 @Test
210 public void testEvents() throws InterruptedException {
211 //shamelessly copy other distributed store tests
212 final CountDownLatch addLatch = new CountDownLatch(1);
213
214 MastershipStoreDelegate checkAdd = new MastershipStoreDelegate() {
215 @Override
216 public void notify(MastershipEvent event) {
217 assertEquals("wrong event:", Type.MASTER_CHANGED, event.type());
218 assertEquals("wrong subject", DID1, event.subject());
219 assertEquals("wrong subject", N1, event.master());
220 addLatch.countDown();
221 }
222 };
223
224 dms.setDelegate(checkAdd);
225 dms.setMaster(N1, DID1);
226 //this will fail until we do something about single-instance-ness
227 assertTrue("Add event fired", addLatch.await(1, TimeUnit.SECONDS));
228 }
229
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700230 private class TestDistributedMastershipStore extends
231 DistributedMastershipStore {
232 public TestDistributedMastershipStore(StoreService storeService,
Ayaka Koshibe4c891272014-10-08 17:14:16 -0700233 KryoSerializer kryoSerialization) {
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700234 this.storeService = storeService;
Ayaka Koshibe4c891272014-10-08 17:14:16 -0700235 this.serializer = kryoSerialization;
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700236 }
237
238 //helper to populate master/backup structures
239 public void put(DeviceId dev, NodeId node,
Ayaka Koshibec4047702014-10-07 14:43:52 -0700240 boolean master, boolean backup, boolean term) {
241 byte [] n = serialize(node);
242 byte [] d = serialize(dev);
243
244 if (master) {
245 dms.masters.put(d, n);
246 dms.unusable.put(d, n);
247 dms.standbys.remove(d, n);
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700248 }
249 if (backup) {
Ayaka Koshibec4047702014-10-07 14:43:52 -0700250 dms.standbys.put(d, n);
251 dms.masters.remove(d, n);
252 dms.unusable.remove(d, n);
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700253 }
254 if (term) {
Ayaka Koshibec4047702014-10-07 14:43:52 -0700255 dms.terms.put(d, 0);
256 }
257 }
258
Ayaka Koshibe4c891272014-10-08 17:14:16 -0700259 //a dumb utility function.
Ayaka Koshibec4047702014-10-07 14:43:52 -0700260 public void dump() {
261 System.out.println("standbys");
262 for (Map.Entry<byte [], byte []> e : standbys.entrySet()) {
263 System.out.println(deserialize(e.getKey()) + ":" + deserialize(e.getValue()));
264 }
265 System.out.println("unusable");
266 for (Map.Entry<byte [], byte []> e : unusable.entrySet()) {
267 System.out.println(deserialize(e.getKey()) + ":" + deserialize(e.getValue()));
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700268 }
269 }
270
271 //clears structures
272 public void reset(boolean store, boolean backup, boolean term) {
273 if (store) {
Ayaka Koshibec4047702014-10-07 14:43:52 -0700274 dms.masters.clear();
275 dms.unusable.clear();
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700276 }
277 if (backup) {
Ayaka Koshibec4047702014-10-07 14:43:52 -0700278 dms.standbys.clear();
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700279 }
280 if (term) {
Ayaka Koshibec4047702014-10-07 14:43:52 -0700281 dms.terms.clear();
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700282 }
283 }
284
285 //increment term for a device
286 public void increment(DeviceId dev) {
Ayaka Koshibec4047702014-10-07 14:43:52 -0700287 Integer t = dms.terms.get(serialize(dev));
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700288 if (t != null) {
Ayaka Koshibec4047702014-10-07 14:43:52 -0700289 dms.terms.put(serialize(dev), ++t);
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700290 }
291 }
292
293 //sets the "local" node
294 public void setCurrent(ControllerNode node) {
295 ((TestClusterService) clusterService).current = node;
296 }
297 }
298
299 private class TestClusterService implements ClusterService {
300
301 protected ControllerNode current;
302
303 @Override
304 public ControllerNode getLocalNode() {
305 return current;
306 }
307
308 @Override
309 public Set<ControllerNode> getNodes() {
310 return Sets.newHashSet(CN1, CN2);
311 }
312
313 @Override
314 public ControllerNode getNode(NodeId nodeId) {
315 return null;
316 }
317
318 @Override
319 public State getState(NodeId nodeId) {
320 return null;
321 }
322
323 @Override
324 public void addListener(ClusterEventListener listener) {
325 }
326
327 @Override
328 public void removeListener(ClusterEventListener listener) {
329 }
330
331 }
Ayaka Koshibe25fd23a2014-10-03 15:50:43 -0700332
Ayaka Koshibe8583ff32014-10-02 16:25:30 -0700333}