Ayaka Koshibe

tests for DistributedMastershipStore

Change-Id: Ic7daa333ac7d7947155b745daf08e4771f1189ef
...@@ -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 +}