blob: 89ee8d3a37bfced7e2dea0e49ee9a98475c2ffeb [file] [log] [blame]
Jonathan Hart7061acd2015-03-04 13:15:32 -08001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2016-present Open Networking Laboratory
Jonathan Hart7061acd2015-03-04 13:15:32 -08003 *
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.HashSet;
Madan Jampania4a59942016-05-02 11:25:34 -070037import java.util.List;
Jonathan Hart7061acd2015-03-04 13:15:32 -080038import java.util.Objects;
39import java.util.Set;
Madan Jampania4a59942016-05-02 11:25:34 -070040
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 Jampania4a59942016-05-02 11:25:34 -0700103 List<NodeId> allNodes = Arrays.asList(MY_NODE_ID, OTHER_NODE_ID);
Jonathan Hart7061acd2015-03-04 13:15:32 -0800104 for (int i = 0; i < numMine; i++) {
Madan Jampania4a59942016-05-02 11:25:34 -0700105 expect(leadershipService.getLeadership(ELECTION_PREFIX + i))
106 .andReturn(new Leadership(ELECTION_PREFIX + i,
107 new Leader(MY_NODE_ID, 1, 1000),
108 allNodes))
109 .anyTimes();
Jonathan Hart7061acd2015-03-04 13:15:32 -0800110 }
111
Madan Jampani1c965102016-01-13 14:34:16 -0800112 for (int i = numMine; i < IntentPartitionManager.NUM_PARTITIONS; i++) {
Madan Jampania4a59942016-05-02 11:25:34 -0700113 expect(leadershipService.getLeadership(ELECTION_PREFIX + i))
114 .andReturn(new Leadership(ELECTION_PREFIX + i,
115 new Leader(OTHER_NODE_ID, 1, 1000),
116 allNodes))
117 .anyTimes();
Jonathan Hart7061acd2015-03-04 13:15:32 -0800118 }
Jonathan Hart7061acd2015-03-04 13:15:32 -0800119 }
120
121 /**
122 * Tests that the PartitionManager's activate method correctly runs for
123 * all the leader elections that it should.
124 */
125 @Test
126 public void testActivate() {
127 reset(leadershipService);
128
129 leadershipService.addListener(anyObject(LeadershipEventListener.class));
130
Madan Jampani1c965102016-01-13 14:34:16 -0800131 for (int i = 0; i < IntentPartitionManager.NUM_PARTITIONS; i++) {
Madan Jampanide003d92015-05-11 17:14:20 -0700132 expect(leadershipService.runForLeadership(ELECTION_PREFIX + i))
Madan Jampani620f70d2016-01-30 22:22:47 -0800133 .andReturn(null)
Madan Jampanide003d92015-05-11 17:14:20 -0700134 .times(1);
Jonathan Hart7061acd2015-03-04 13:15:32 -0800135 }
136
137 replay(leadershipService);
138
139 partitionManager.activate();
140
141 verify(leadershipService);
142 }
143
144 /**
145 * Tests that the isMine method returns the correct result based on the
146 * underlying leadership service data.
147 */
148 @Test
149 public void testIsMine() {
150 // We'll own only the first partition
151 setUpLeadershipService(1);
152 replay(leadershipService);
153
154 Key myKey = new ControllableHashKey(0);
155 Key notMyKey = new ControllableHashKey(1);
156
157 assertTrue(partitionManager.isMine(myKey));
158 assertFalse(partitionManager.isMine(notMyKey));
159
160 // Make us the owner of 4 partitions now
161 reset(leadershipService);
162 setUpLeadershipService(4);
163 replay(leadershipService);
164
165 assertTrue(partitionManager.isMine(myKey));
166 // notMyKey is now my key because because we're in control of that
167 // partition now
168 assertTrue(partitionManager.isMine(notMyKey));
169
170 assertFalse(partitionManager.isMine(new ControllableHashKey(4)));
171 }
172
173 /**
174 * Tests sending in LeadershipServiceEvents in the case when we have
175 * too many partitions. The event will trigger the partition manager to
Madan Jampani4732c1b2015-05-19 17:11:50 -0700176 * schedule a rebalancing activity.
Jonathan Hart7061acd2015-03-04 13:15:32 -0800177 */
178 @Test
Madan Jampani4732c1b2015-05-19 17:11:50 -0700179 public void testRebalanceScheduling() {
Jonathan Hart7061acd2015-03-04 13:15:32 -0800180 // We have all the partitions so we'll need to relinquish some
Madan Jampani1c965102016-01-13 14:34:16 -0800181 setUpLeadershipService(IntentPartitionManager.NUM_PARTITIONS);
Jonathan Hart7061acd2015-03-04 13:15:32 -0800182
Jonathan Hart7061acd2015-03-04 13:15:32 -0800183 replay(leadershipService);
184
185 partitionManager.activate();
186 // Send in the event
187 leaderListener.event(event);
188
Madan Jampani4732c1b2015-05-19 17:11:50 -0700189 assertTrue(partitionManager.rebalanceScheduled.get());
190
Jonathan Hart7061acd2015-03-04 13:15:32 -0800191 verify(leadershipService);
192 }
193
194 /**
Madan Jampani4732c1b2015-05-19 17:11:50 -0700195 * Tests rebalance will trigger the right now of leadership withdraw calls.
Jonathan Hart7061acd2015-03-04 13:15:32 -0800196 */
197 @Test
Madan Jampani4732c1b2015-05-19 17:11:50 -0700198 public void testRebalance() {
199 // We have all the partitions so we'll need to relinquish some
Madan Jampani1c965102016-01-13 14:34:16 -0800200 setUpLeadershipService(IntentPartitionManager.NUM_PARTITIONS);
Madan Jampani4732c1b2015-05-19 17:11:50 -0700201
Madan Jampani620f70d2016-01-30 22:22:47 -0800202 leadershipService.withdraw(anyString());
203 expectLastCall().times(7);
Madan Jampani4732c1b2015-05-19 17:11:50 -0700204
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
Madan Jampani1c965102016-01-13 14:34:16 -0800222 setUpLeadershipService(IntentPartitionManager.NUM_PARTITIONS / 2);
Jonathan Hart7061acd2015-03-04 13:15:32 -0800223 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
Madan Jampani1c965102016-01-13 14:34:16 -0800234 setUpLeadershipService(IntentPartitionManager.NUM_PARTITIONS / 2 - 1);
Jonathan Hart7061acd2015-03-04 13:15:32 -0800235 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 }
Simon Hunte2b6a2b2015-10-27 11:38:51 -0700326
327 @Override
328 public int compareTo(Key o) {
329 Long thisHash = hash();
330 return thisHash.compareTo(o.hash());
331 }
Jonathan Hart7061acd2015-03-04 13:15:32 -0800332 }
333}