blob: 3b091c04ee30da611feb94a2e5f14c92a9ee1169 [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;
25import org.onosproject.cluster.Leadership;
26import org.onosproject.cluster.LeadershipEvent;
27import org.onosproject.cluster.LeadershipEventListener;
28import org.onosproject.cluster.LeadershipService;
29import org.onosproject.cluster.LeadershipServiceAdapter;
30import org.onosproject.cluster.NodeId;
31import org.onosproject.net.intent.Key;
32
33import java.util.HashMap;
34import java.util.HashSet;
35import java.util.Map;
36import java.util.Objects;
37import java.util.Set;
Madan Jampanide003d92015-05-11 17:14:20 -070038import java.util.concurrent.CompletableFuture;
Jonathan Hart7061acd2015-03-04 13:15:32 -080039
40import static junit.framework.TestCase.assertFalse;
41import static org.easymock.EasyMock.anyObject;
42import static org.easymock.EasyMock.anyString;
43import static org.easymock.EasyMock.createMock;
44import static org.easymock.EasyMock.expect;
45import static org.easymock.EasyMock.expectLastCall;
46import static org.easymock.EasyMock.replay;
47import static org.easymock.EasyMock.reset;
48import static org.easymock.EasyMock.verify;
49import static org.junit.Assert.assertTrue;
50
51/**
52 * Unit tests for the PartitionManager class.
53 */
54public class PartitionManagerTest {
55
56 private final LeadershipEvent event
57 = new LeadershipEvent(LeadershipEvent.Type.LEADER_ELECTED,
58 new Leadership(ELECTION_PREFIX + "0",
59 MY_NODE_ID, 0, 0));
60
61 private static final NodeId MY_NODE_ID = new NodeId("local");
62 private static final NodeId OTHER_NODE_ID = new NodeId("other");
63 private static final NodeId INACTIVE_NODE_ID = new NodeId("inactive");
64
65 private static final String ELECTION_PREFIX = "intent-partition-";
66
67 private LeadershipService leadershipService;
68 private LeadershipEventListener leaderListener;
69
70 private PartitionManager partitionManager;
71
72 @Before
73 public void setUp() {
74 leadershipService = createMock(LeadershipService.class);
75
76 leadershipService.addListener(anyObject(LeadershipEventListener.class));
77 expectLastCall().andDelegateTo(new TestLeadershipService());
Madan Jampanide003d92015-05-11 17:14:20 -070078 for (int i = 0; i < PartitionManager.NUM_PARTITIONS; i++) {
79 expect(leadershipService.runForLeadership(ELECTION_PREFIX + i))
80 .andReturn(CompletableFuture.completedFuture(null))
81 .times(1);
82 }
Jonathan Hart7061acd2015-03-04 13:15:32 -080083
84 partitionManager = new PartitionManager()
85 .withScheduledExecutor(new NullScheduledExecutor());
86
87 partitionManager.clusterService = new TestClusterService();
88 partitionManager.leadershipService = leadershipService;
89 }
90
91 /**
92 * Configures a mock leadership service to have the specified number of
93 * partitions owned by the local node and all other partitions owned by a
94 * (fake) remote node.
95 *
96 * @param numMine number of partitions that should be owned by the local node
97 */
98 private void setUpLeadershipService(int numMine) {
Madan Jampanide003d92015-05-11 17:14:20 -070099
Jonathan Hart7061acd2015-03-04 13:15:32 -0800100 Map<String, Leadership> leaderBoard = new HashMap<>();
101
102 for (int i = 0; i < numMine; i++) {
103 expect(leadershipService.getLeader(ELECTION_PREFIX + i))
104 .andReturn(MY_NODE_ID).anyTimes();
105 leaderBoard.put(ELECTION_PREFIX + i,
106 new Leadership(ELECTION_PREFIX + i, MY_NODE_ID, 0, 0));
107 }
108
109 for (int i = numMine; i < PartitionManager.NUM_PARTITIONS; i++) {
110 expect(leadershipService.getLeader(ELECTION_PREFIX + i))
111 .andReturn(OTHER_NODE_ID).anyTimes();
112
113 leaderBoard.put(ELECTION_PREFIX + i,
114 new Leadership(ELECTION_PREFIX + i, OTHER_NODE_ID, 0, 0));
115 }
116
117 expect(leadershipService.getLeaderBoard()).andReturn(leaderBoard).anyTimes();
118 }
119
120 /**
121 * Tests that the PartitionManager's activate method correctly runs for
122 * all the leader elections that it should.
123 */
124 @Test
125 public void testActivate() {
126 reset(leadershipService);
127
128 leadershipService.addListener(anyObject(LeadershipEventListener.class));
129
130 for (int i = 0; i < PartitionManager.NUM_PARTITIONS; i++) {
Madan Jampanide003d92015-05-11 17:14:20 -0700131 expect(leadershipService.runForLeadership(ELECTION_PREFIX + i))
132 .andReturn(CompletableFuture.completedFuture(null))
133 .times(1);
Jonathan Hart7061acd2015-03-04 13:15:32 -0800134 }
135
136 replay(leadershipService);
137
138 partitionManager.activate();
139
140 verify(leadershipService);
141 }
142
143 /**
144 * Tests that the isMine method returns the correct result based on the
145 * underlying leadership service data.
146 */
147 @Test
148 public void testIsMine() {
149 // We'll own only the first partition
150 setUpLeadershipService(1);
151 replay(leadershipService);
152
153 Key myKey = new ControllableHashKey(0);
154 Key notMyKey = new ControllableHashKey(1);
155
156 assertTrue(partitionManager.isMine(myKey));
157 assertFalse(partitionManager.isMine(notMyKey));
158
159 // Make us the owner of 4 partitions now
160 reset(leadershipService);
161 setUpLeadershipService(4);
162 replay(leadershipService);
163
164 assertTrue(partitionManager.isMine(myKey));
165 // notMyKey is now my key because because we're in control of that
166 // partition now
167 assertTrue(partitionManager.isMine(notMyKey));
168
169 assertFalse(partitionManager.isMine(new ControllableHashKey(4)));
170 }
171
172 /**
173 * Tests sending in LeadershipServiceEvents in the case when we have
174 * too many partitions. The event will trigger the partition manager to
Madan Jampani4732c1b2015-05-19 17:11:50 -0700175 * schedule a rebalancing activity.
Jonathan Hart7061acd2015-03-04 13:15:32 -0800176 */
177 @Test
Madan Jampani4732c1b2015-05-19 17:11:50 -0700178 public void testRebalanceScheduling() {
Jonathan Hart7061acd2015-03-04 13:15:32 -0800179 // We have all the partitions so we'll need to relinquish some
180 setUpLeadershipService(PartitionManager.NUM_PARTITIONS);
181
Jonathan Hart7061acd2015-03-04 13:15:32 -0800182 replay(leadershipService);
183
184 partitionManager.activate();
185 // Send in the event
186 leaderListener.event(event);
187
Madan Jampani4732c1b2015-05-19 17:11:50 -0700188 assertTrue(partitionManager.rebalanceScheduled.get());
189
Jonathan Hart7061acd2015-03-04 13:15:32 -0800190 verify(leadershipService);
191 }
192
193 /**
Madan Jampani4732c1b2015-05-19 17:11:50 -0700194 * Tests rebalance will trigger the right now of leadership withdraw calls.
Jonathan Hart7061acd2015-03-04 13:15:32 -0800195 */
196 @Test
Madan Jampani4732c1b2015-05-19 17:11:50 -0700197 public void testRebalance() {
198 // We have all the partitions so we'll need to relinquish some
199 setUpLeadershipService(PartitionManager.NUM_PARTITIONS);
200
201 expect(leadershipService.withdraw(anyString()))
202 .andReturn(CompletableFuture.completedFuture(null))
203 .times(7);
204
205 replay(leadershipService);
206
207 partitionManager.activate();
208
209 // trigger rebalance
210 partitionManager.doRebalance();
211
212 verify(leadershipService);
213 }
214
215 /**
216 * Tests that attempts to rebalance when the paritions are already
217 * evenly distributed does not result in any relinquish attempts.
218 */
219 @Test
220 public void testNoRebalance() {
Jonathan Hart7061acd2015-03-04 13:15:32 -0800221 // Partitions are already perfectly balanced among the two active instances
222 setUpLeadershipService(PartitionManager.NUM_PARTITIONS / 2);
223 replay(leadershipService);
224
225 partitionManager.activate();
226
Madan Jampani4732c1b2015-05-19 17:11:50 -0700227 // trigger rebalance
228 partitionManager.doRebalance();
Jonathan Hart7061acd2015-03-04 13:15:32 -0800229
230 verify(leadershipService);
231
232 reset(leadershipService);
233 // We have a smaller share than we should
234 setUpLeadershipService(PartitionManager.NUM_PARTITIONS / 2 - 1);
235 replay(leadershipService);
236
Madan Jampani4732c1b2015-05-19 17:11:50 -0700237 // trigger rebalance
238 partitionManager.doRebalance();
Jonathan Hart7061acd2015-03-04 13:15:32 -0800239
240 verify(leadershipService);
241 }
242
243 /**
244 * LeadershipService that allows us to grab a reference to
245 * PartitionManager's LeadershipEventListener.
246 */
247 public class TestLeadershipService extends LeadershipServiceAdapter {
248 @Override
249 public void addListener(LeadershipEventListener listener) {
250 leaderListener = listener;
251 }
252 }
253
254 /**
255 * ClusterService set up with a very simple cluster - 3 nodes, one is the
256 * current node, one is a different active node, and one is an inactive node.
257 */
258 private class TestClusterService extends ClusterServiceAdapter {
259
260 private final ControllerNode self =
261 new DefaultControllerNode(MY_NODE_ID, IpAddress.valueOf(1));
262 private final ControllerNode otherNode =
263 new DefaultControllerNode(OTHER_NODE_ID, IpAddress.valueOf(2));
264 private final ControllerNode inactiveNode =
265 new DefaultControllerNode(INACTIVE_NODE_ID, IpAddress.valueOf(3));
266
267 Set<ControllerNode> nodes;
268
269 public TestClusterService() {
270 nodes = new HashSet<>();
271 nodes.add(self);
272 nodes.add(otherNode);
273 nodes.add(inactiveNode);
274 }
275
276 @Override
277 public ControllerNode getLocalNode() {
278 return self;
279 }
280
281 @Override
282 public Set<ControllerNode> getNodes() {
283 return nodes;
284 }
285
286 @Override
287 public ControllerNode getNode(NodeId nodeId) {
288 return nodes.stream()
289 .filter(c -> c.id().equals(nodeId))
290 .findFirst()
291 .get();
292 }
293
294 @Override
295 public ControllerNode.State getState(NodeId nodeId) {
296 return nodeId.equals(INACTIVE_NODE_ID) ? ControllerNode.State.INACTIVE :
297 ControllerNode.State.ACTIVE;
298 }
299 }
300
301 /**
302 * A key that always hashes to a value provided to the constructor. This
303 * allows us to control the hash of the key for unit tests.
304 */
305 private class ControllableHashKey extends Key {
306
307 protected ControllableHashKey(long hash) {
308 super(hash);
309 }
310
311 @Override
312 public int hashCode() {
313 return Objects.hash(hash());
314 }
315
316 @Override
317 public boolean equals(Object obj) {
318 if (!(obj instanceof ControllableHashKey)) {
319 return false;
320 }
321
322 ControllableHashKey that = (ControllableHashKey) obj;
323
324 return Objects.equals(this.hash(), that.hash());
325 }
326 }
327}