blob: 9655b2de0e09c7e3ba3eef64e027c42167c74a4e [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
175 * reassess how many partitions it has and relinquish some.
176 */
177 @Test
178 public void testRelinquish() {
179 // We have all the partitions so we'll need to relinquish some
180 setUpLeadershipService(PartitionManager.NUM_PARTITIONS);
181
Madan Jampanide003d92015-05-11 17:14:20 -0700182 expect(leadershipService.withdraw(anyString()))
183 .andReturn(CompletableFuture.completedFuture(null))
184 .times(7);
Jonathan Hart7061acd2015-03-04 13:15:32 -0800185
186 replay(leadershipService);
187
188 partitionManager.activate();
189 // Send in the event
190 leaderListener.event(event);
191
192 verify(leadershipService);
193 }
194
195 /**
196 * Tests sending in LeadershipServiceEvents in the case when we have the
197 * right amount or too many partitions. These events will not trigger any
198 * partition reassignments.
199 */
200 @Test
201 public void testNoRelinquish() {
202 // Partitions are already perfectly balanced among the two active instances
203 setUpLeadershipService(PartitionManager.NUM_PARTITIONS / 2);
204 replay(leadershipService);
205
206 partitionManager.activate();
207
208 // Send in the event
209 leaderListener.event(event);
210
211 verify(leadershipService);
212
213 reset(leadershipService);
214 // We have a smaller share than we should
215 setUpLeadershipService(PartitionManager.NUM_PARTITIONS / 2 - 1);
216 replay(leadershipService);
217
218 // Send in the event
219 leaderListener.event(event);
220
221 verify(leadershipService);
222 }
223
224 /**
225 * LeadershipService that allows us to grab a reference to
226 * PartitionManager's LeadershipEventListener.
227 */
228 public class TestLeadershipService extends LeadershipServiceAdapter {
229 @Override
230 public void addListener(LeadershipEventListener listener) {
231 leaderListener = listener;
232 }
233 }
234
235 /**
236 * ClusterService set up with a very simple cluster - 3 nodes, one is the
237 * current node, one is a different active node, and one is an inactive node.
238 */
239 private class TestClusterService extends ClusterServiceAdapter {
240
241 private final ControllerNode self =
242 new DefaultControllerNode(MY_NODE_ID, IpAddress.valueOf(1));
243 private final ControllerNode otherNode =
244 new DefaultControllerNode(OTHER_NODE_ID, IpAddress.valueOf(2));
245 private final ControllerNode inactiveNode =
246 new DefaultControllerNode(INACTIVE_NODE_ID, IpAddress.valueOf(3));
247
248 Set<ControllerNode> nodes;
249
250 public TestClusterService() {
251 nodes = new HashSet<>();
252 nodes.add(self);
253 nodes.add(otherNode);
254 nodes.add(inactiveNode);
255 }
256
257 @Override
258 public ControllerNode getLocalNode() {
259 return self;
260 }
261
262 @Override
263 public Set<ControllerNode> getNodes() {
264 return nodes;
265 }
266
267 @Override
268 public ControllerNode getNode(NodeId nodeId) {
269 return nodes.stream()
270 .filter(c -> c.id().equals(nodeId))
271 .findFirst()
272 .get();
273 }
274
275 @Override
276 public ControllerNode.State getState(NodeId nodeId) {
277 return nodeId.equals(INACTIVE_NODE_ID) ? ControllerNode.State.INACTIVE :
278 ControllerNode.State.ACTIVE;
279 }
280 }
281
282 /**
283 * A key that always hashes to a value provided to the constructor. This
284 * allows us to control the hash of the key for unit tests.
285 */
286 private class ControllableHashKey extends Key {
287
288 protected ControllableHashKey(long hash) {
289 super(hash);
290 }
291
292 @Override
293 public int hashCode() {
294 return Objects.hash(hash());
295 }
296
297 @Override
298 public boolean equals(Object obj) {
299 if (!(obj instanceof ControllableHashKey)) {
300 return false;
301 }
302
303 ControllableHashKey that = (ControllableHashKey) obj;
304
305 return Objects.equals(this.hash(), that.hash());
306 }
307 }
308}