blob: e9ded9a4802daa310c2c284baddc11c855dd31d4 [file] [log] [blame]
Jonathan Hart7061acd2015-03-04 13:15:32 -08001/*
2 * Copyright 2015 Open Networking Laboratory
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.onosproject.store.intent.impl;
17
18import org.junit.Before;
19import org.junit.Test;
20import org.onlab.junit.NullScheduledExecutor;
21import org.onlab.packet.IpAddress;
22import org.onosproject.cluster.ClusterServiceAdapter;
23import org.onosproject.cluster.ControllerNode;
24import org.onosproject.cluster.DefaultControllerNode;
Madan Jampani620f70d2016-01-30 22:22:47 -080025import org.onosproject.cluster.Leader;
Jonathan Hart7061acd2015-03-04 13:15:32 -080026import org.onosproject.cluster.Leadership;
27import org.onosproject.cluster.LeadershipEvent;
28import org.onosproject.cluster.LeadershipEventListener;
29import org.onosproject.cluster.LeadershipService;
30import org.onosproject.cluster.LeadershipServiceAdapter;
31import org.onosproject.cluster.NodeId;
Brian O'Connor69d6ac72015-05-29 16:24:06 -070032import org.onosproject.common.event.impl.TestEventDispatcher;
Jonathan Hart7061acd2015-03-04 13:15:32 -080033import org.onosproject.net.intent.Key;
34
Madan Jampani620f70d2016-01-30 22:22:47 -080035import java.util.Arrays;
Jonathan Hart7061acd2015-03-04 13:15:32 -080036import java.util.HashMap;
37import java.util.HashSet;
38import java.util.Map;
39import java.util.Objects;
40import java.util.Set;
Jonathan Hart7061acd2015-03-04 13:15:32 -080041import static junit.framework.TestCase.assertFalse;
42import static org.easymock.EasyMock.anyObject;
43import static org.easymock.EasyMock.anyString;
44import static org.easymock.EasyMock.createMock;
45import static org.easymock.EasyMock.expect;
46import static org.easymock.EasyMock.expectLastCall;
47import static org.easymock.EasyMock.replay;
48import static org.easymock.EasyMock.reset;
49import static org.easymock.EasyMock.verify;
50import static org.junit.Assert.assertTrue;
51
52/**
Madan Jampani1c965102016-01-13 14:34:16 -080053 * Unit tests for the IntentPartitionManager class.
Jonathan Hart7061acd2015-03-04 13:15:32 -080054 */
Madan Jampani1c965102016-01-13 14:34:16 -080055public class IntentPartitionManagerTest {
Jonathan Hart7061acd2015-03-04 13:15:32 -080056
57 private final LeadershipEvent event
Madan Jampani620f70d2016-01-30 22:22:47 -080058 = new LeadershipEvent(LeadershipEvent.Type.CANDIDATES_CHANGED,
Jonathan Hart7061acd2015-03-04 13:15:32 -080059 new Leadership(ELECTION_PREFIX + "0",
Madan Jampani620f70d2016-01-30 22:22:47 -080060 new Leader(MY_NODE_ID, 0, 0),
61 Arrays.asList(MY_NODE_ID, OTHER_NODE_ID)));
Jonathan Hart7061acd2015-03-04 13:15:32 -080062
63 private static final NodeId MY_NODE_ID = new NodeId("local");
64 private static final NodeId OTHER_NODE_ID = new NodeId("other");
65 private static final NodeId INACTIVE_NODE_ID = new NodeId("inactive");
66
67 private static final String ELECTION_PREFIX = "intent-partition-";
68
69 private LeadershipService leadershipService;
70 private LeadershipEventListener leaderListener;
71
Madan Jampani1c965102016-01-13 14:34:16 -080072 private IntentPartitionManager partitionManager;
Jonathan Hart7061acd2015-03-04 13:15:32 -080073
74 @Before
75 public void setUp() {
76 leadershipService = createMock(LeadershipService.class);
77
78 leadershipService.addListener(anyObject(LeadershipEventListener.class));
79 expectLastCall().andDelegateTo(new TestLeadershipService());
Madan Jampani1c965102016-01-13 14:34:16 -080080 for (int i = 0; i < IntentPartitionManager.NUM_PARTITIONS; i++) {
Madan Jampanide003d92015-05-11 17:14:20 -070081 expect(leadershipService.runForLeadership(ELECTION_PREFIX + i))
Madan Jampani620f70d2016-01-30 22:22:47 -080082 .andReturn(null)
Madan Jampanide003d92015-05-11 17:14:20 -070083 .times(1);
84 }
Jonathan Hart7061acd2015-03-04 13:15:32 -080085
Madan Jampani1c965102016-01-13 14:34:16 -080086 partitionManager = new IntentPartitionManager()
Jonathan Hart7061acd2015-03-04 13:15:32 -080087 .withScheduledExecutor(new NullScheduledExecutor());
88
89 partitionManager.clusterService = new TestClusterService();
Madan Jampania9673fd2016-02-02 13:01:29 -080090 partitionManager.localNodeId = MY_NODE_ID;
Jonathan Hart7061acd2015-03-04 13:15:32 -080091 partitionManager.leadershipService = leadershipService;
Brian O'Connor69d6ac72015-05-29 16:24:06 -070092 partitionManager.eventDispatcher = new TestEventDispatcher();
Jonathan Hart7061acd2015-03-04 13:15:32 -080093 }
94
95 /**
96 * Configures a mock leadership service to have the specified number of
97 * partitions owned by the local node and all other partitions owned by a
98 * (fake) remote node.
99 *
100 * @param numMine number of partitions that should be owned by the local node
101 */
102 private void setUpLeadershipService(int numMine) {
Madan Jampanide003d92015-05-11 17:14:20 -0700103
Jonathan Hart7061acd2015-03-04 13:15:32 -0800104 Map<String, Leadership> leaderBoard = new HashMap<>();
105
106 for (int i = 0; i < numMine; i++) {
107 expect(leadershipService.getLeader(ELECTION_PREFIX + i))
108 .andReturn(MY_NODE_ID).anyTimes();
109 leaderBoard.put(ELECTION_PREFIX + i,
Madan Jampani620f70d2016-01-30 22:22:47 -0800110 new Leadership(ELECTION_PREFIX + i,
111 new Leader(MY_NODE_ID, 0, 0),
112 Arrays.asList(MY_NODE_ID)));
Jonathan Hart7061acd2015-03-04 13:15:32 -0800113 }
114
Madan Jampani1c965102016-01-13 14:34:16 -0800115 for (int i = numMine; i < IntentPartitionManager.NUM_PARTITIONS; i++) {
Jonathan Hart7061acd2015-03-04 13:15:32 -0800116 expect(leadershipService.getLeader(ELECTION_PREFIX + i))
117 .andReturn(OTHER_NODE_ID).anyTimes();
118
119 leaderBoard.put(ELECTION_PREFIX + i,
Madan Jampani620f70d2016-01-30 22:22:47 -0800120 new Leadership(ELECTION_PREFIX + i,
121 new Leader(OTHER_NODE_ID, 0, 0),
122 Arrays.asList(OTHER_NODE_ID)));
Jonathan Hart7061acd2015-03-04 13:15:32 -0800123 }
124
125 expect(leadershipService.getLeaderBoard()).andReturn(leaderBoard).anyTimes();
126 }
127
128 /**
129 * Tests that the PartitionManager's activate method correctly runs for
130 * all the leader elections that it should.
131 */
132 @Test
133 public void testActivate() {
134 reset(leadershipService);
135
136 leadershipService.addListener(anyObject(LeadershipEventListener.class));
137
Madan Jampani1c965102016-01-13 14:34:16 -0800138 for (int i = 0; i < IntentPartitionManager.NUM_PARTITIONS; i++) {
Madan Jampanide003d92015-05-11 17:14:20 -0700139 expect(leadershipService.runForLeadership(ELECTION_PREFIX + i))
Madan Jampani620f70d2016-01-30 22:22:47 -0800140 .andReturn(null)
Madan Jampanide003d92015-05-11 17:14:20 -0700141 .times(1);
Jonathan Hart7061acd2015-03-04 13:15:32 -0800142 }
143
144 replay(leadershipService);
145
146 partitionManager.activate();
147
148 verify(leadershipService);
149 }
150
151 /**
152 * Tests that the isMine method returns the correct result based on the
153 * underlying leadership service data.
154 */
155 @Test
156 public void testIsMine() {
157 // We'll own only the first partition
158 setUpLeadershipService(1);
159 replay(leadershipService);
160
161 Key myKey = new ControllableHashKey(0);
162 Key notMyKey = new ControllableHashKey(1);
163
164 assertTrue(partitionManager.isMine(myKey));
165 assertFalse(partitionManager.isMine(notMyKey));
166
167 // Make us the owner of 4 partitions now
168 reset(leadershipService);
169 setUpLeadershipService(4);
170 replay(leadershipService);
171
172 assertTrue(partitionManager.isMine(myKey));
173 // notMyKey is now my key because because we're in control of that
174 // partition now
175 assertTrue(partitionManager.isMine(notMyKey));
176
177 assertFalse(partitionManager.isMine(new ControllableHashKey(4)));
178 }
179
180 /**
181 * Tests sending in LeadershipServiceEvents in the case when we have
182 * too many partitions. The event will trigger the partition manager to
Madan Jampani4732c1b2015-05-19 17:11:50 -0700183 * schedule a rebalancing activity.
Jonathan Hart7061acd2015-03-04 13:15:32 -0800184 */
185 @Test
Madan Jampani4732c1b2015-05-19 17:11:50 -0700186 public void testRebalanceScheduling() {
Jonathan Hart7061acd2015-03-04 13:15:32 -0800187 // We have all the partitions so we'll need to relinquish some
Madan Jampani1c965102016-01-13 14:34:16 -0800188 setUpLeadershipService(IntentPartitionManager.NUM_PARTITIONS);
Jonathan Hart7061acd2015-03-04 13:15:32 -0800189
Jonathan Hart7061acd2015-03-04 13:15:32 -0800190 replay(leadershipService);
191
192 partitionManager.activate();
193 // Send in the event
194 leaderListener.event(event);
195
Madan Jampani4732c1b2015-05-19 17:11:50 -0700196 assertTrue(partitionManager.rebalanceScheduled.get());
197
Jonathan Hart7061acd2015-03-04 13:15:32 -0800198 verify(leadershipService);
199 }
200
201 /**
Madan Jampani4732c1b2015-05-19 17:11:50 -0700202 * Tests rebalance will trigger the right now of leadership withdraw calls.
Jonathan Hart7061acd2015-03-04 13:15:32 -0800203 */
204 @Test
Madan Jampani4732c1b2015-05-19 17:11:50 -0700205 public void testRebalance() {
206 // We have all the partitions so we'll need to relinquish some
Madan Jampani1c965102016-01-13 14:34:16 -0800207 setUpLeadershipService(IntentPartitionManager.NUM_PARTITIONS);
Madan Jampani4732c1b2015-05-19 17:11:50 -0700208
Madan Jampani620f70d2016-01-30 22:22:47 -0800209 leadershipService.withdraw(anyString());
210 expectLastCall().times(7);
Madan Jampani4732c1b2015-05-19 17:11:50 -0700211
212 replay(leadershipService);
213
214 partitionManager.activate();
215
216 // trigger rebalance
217 partitionManager.doRebalance();
218
219 verify(leadershipService);
220 }
221
222 /**
223 * Tests that attempts to rebalance when the paritions are already
224 * evenly distributed does not result in any relinquish attempts.
225 */
226 @Test
227 public void testNoRebalance() {
Jonathan Hart7061acd2015-03-04 13:15:32 -0800228 // Partitions are already perfectly balanced among the two active instances
Madan Jampani1c965102016-01-13 14:34:16 -0800229 setUpLeadershipService(IntentPartitionManager.NUM_PARTITIONS / 2);
Jonathan Hart7061acd2015-03-04 13:15:32 -0800230 replay(leadershipService);
231
232 partitionManager.activate();
233
Madan Jampani4732c1b2015-05-19 17:11:50 -0700234 // trigger rebalance
235 partitionManager.doRebalance();
Jonathan Hart7061acd2015-03-04 13:15:32 -0800236
237 verify(leadershipService);
238
239 reset(leadershipService);
240 // We have a smaller share than we should
Madan Jampani1c965102016-01-13 14:34:16 -0800241 setUpLeadershipService(IntentPartitionManager.NUM_PARTITIONS / 2 - 1);
Jonathan Hart7061acd2015-03-04 13:15:32 -0800242 replay(leadershipService);
243
Madan Jampani4732c1b2015-05-19 17:11:50 -0700244 // trigger rebalance
245 partitionManager.doRebalance();
Jonathan Hart7061acd2015-03-04 13:15:32 -0800246
247 verify(leadershipService);
248 }
249
250 /**
251 * LeadershipService that allows us to grab a reference to
252 * PartitionManager's LeadershipEventListener.
253 */
254 public class TestLeadershipService extends LeadershipServiceAdapter {
255 @Override
256 public void addListener(LeadershipEventListener listener) {
257 leaderListener = listener;
258 }
259 }
260
261 /**
262 * ClusterService set up with a very simple cluster - 3 nodes, one is the
263 * current node, one is a different active node, and one is an inactive node.
264 */
265 private class TestClusterService extends ClusterServiceAdapter {
266
267 private final ControllerNode self =
268 new DefaultControllerNode(MY_NODE_ID, IpAddress.valueOf(1));
269 private final ControllerNode otherNode =
270 new DefaultControllerNode(OTHER_NODE_ID, IpAddress.valueOf(2));
271 private final ControllerNode inactiveNode =
272 new DefaultControllerNode(INACTIVE_NODE_ID, IpAddress.valueOf(3));
273
274 Set<ControllerNode> nodes;
275
276 public TestClusterService() {
277 nodes = new HashSet<>();
278 nodes.add(self);
279 nodes.add(otherNode);
280 nodes.add(inactiveNode);
281 }
282
283 @Override
284 public ControllerNode getLocalNode() {
285 return self;
286 }
287
288 @Override
289 public Set<ControllerNode> getNodes() {
290 return nodes;
291 }
292
293 @Override
294 public ControllerNode getNode(NodeId nodeId) {
295 return nodes.stream()
296 .filter(c -> c.id().equals(nodeId))
297 .findFirst()
298 .get();
299 }
300
301 @Override
302 public ControllerNode.State getState(NodeId nodeId) {
303 return nodeId.equals(INACTIVE_NODE_ID) ? ControllerNode.State.INACTIVE :
304 ControllerNode.State.ACTIVE;
305 }
306 }
307
308 /**
309 * A key that always hashes to a value provided to the constructor. This
310 * allows us to control the hash of the key for unit tests.
311 */
312 private class ControllableHashKey extends Key {
313
314 protected ControllableHashKey(long hash) {
315 super(hash);
316 }
317
318 @Override
319 public int hashCode() {
320 return Objects.hash(hash());
321 }
322
323 @Override
324 public boolean equals(Object obj) {
325 if (!(obj instanceof ControllableHashKey)) {
326 return false;
327 }
328
329 ControllableHashKey that = (ControllableHashKey) obj;
330
331 return Objects.equals(this.hash(), that.hash());
332 }
Simon Hunte2b6a2b2015-10-27 11:38:51 -0700333
334 @Override
335 public int compareTo(Key o) {
336 Long thisHash = hash();
337 return thisHash.compareTo(o.hash());
338 }
Jonathan Hart7061acd2015-03-04 13:15:32 -0800339 }
340}