blob: c3b529eecb99638493b3224e8b9897f8d25bc76c [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;
38
39import static junit.framework.TestCase.assertFalse;
40import static org.easymock.EasyMock.anyObject;
41import static org.easymock.EasyMock.anyString;
42import static org.easymock.EasyMock.createMock;
43import static org.easymock.EasyMock.expect;
44import static org.easymock.EasyMock.expectLastCall;
45import static org.easymock.EasyMock.replay;
46import static org.easymock.EasyMock.reset;
47import static org.easymock.EasyMock.verify;
48import static org.junit.Assert.assertTrue;
49
50/**
51 * Unit tests for the PartitionManager class.
52 */
53public class PartitionManagerTest {
54
55 private final LeadershipEvent event
56 = new LeadershipEvent(LeadershipEvent.Type.LEADER_ELECTED,
57 new Leadership(ELECTION_PREFIX + "0",
58 MY_NODE_ID, 0, 0));
59
60 private static final NodeId MY_NODE_ID = new NodeId("local");
61 private static final NodeId OTHER_NODE_ID = new NodeId("other");
62 private static final NodeId INACTIVE_NODE_ID = new NodeId("inactive");
63
64 private static final String ELECTION_PREFIX = "intent-partition-";
65
66 private LeadershipService leadershipService;
67 private LeadershipEventListener leaderListener;
68
69 private PartitionManager partitionManager;
70
71 @Before
72 public void setUp() {
73 leadershipService = createMock(LeadershipService.class);
74
75 leadershipService.addListener(anyObject(LeadershipEventListener.class));
76 expectLastCall().andDelegateTo(new TestLeadershipService());
77 leadershipService.runForLeadership(anyString());
78 expectLastCall().anyTimes();
79
80 partitionManager = new PartitionManager()
81 .withScheduledExecutor(new NullScheduledExecutor());
82
83 partitionManager.clusterService = new TestClusterService();
84 partitionManager.leadershipService = leadershipService;
85 }
86
87 /**
88 * Configures a mock leadership service to have the specified number of
89 * partitions owned by the local node and all other partitions owned by a
90 * (fake) remote node.
91 *
92 * @param numMine number of partitions that should be owned by the local node
93 */
94 private void setUpLeadershipService(int numMine) {
95 Map<String, Leadership> leaderBoard = new HashMap<>();
96
97 for (int i = 0; i < numMine; i++) {
98 expect(leadershipService.getLeader(ELECTION_PREFIX + i))
99 .andReturn(MY_NODE_ID).anyTimes();
100 leaderBoard.put(ELECTION_PREFIX + i,
101 new Leadership(ELECTION_PREFIX + i, MY_NODE_ID, 0, 0));
102 }
103
104 for (int i = numMine; i < PartitionManager.NUM_PARTITIONS; i++) {
105 expect(leadershipService.getLeader(ELECTION_PREFIX + i))
106 .andReturn(OTHER_NODE_ID).anyTimes();
107
108 leaderBoard.put(ELECTION_PREFIX + i,
109 new Leadership(ELECTION_PREFIX + i, OTHER_NODE_ID, 0, 0));
110 }
111
112 expect(leadershipService.getLeaderBoard()).andReturn(leaderBoard).anyTimes();
113 }
114
115 /**
116 * Tests that the PartitionManager's activate method correctly runs for
117 * all the leader elections that it should.
118 */
119 @Test
120 public void testActivate() {
121 reset(leadershipService);
122
123 leadershipService.addListener(anyObject(LeadershipEventListener.class));
124
125 for (int i = 0; i < PartitionManager.NUM_PARTITIONS; i++) {
126 leadershipService.runForLeadership(ELECTION_PREFIX + i);
127 }
128
129 replay(leadershipService);
130
131 partitionManager.activate();
132
133 verify(leadershipService);
134 }
135
136 /**
137 * Tests that the isMine method returns the correct result based on the
138 * underlying leadership service data.
139 */
140 @Test
141 public void testIsMine() {
142 // We'll own only the first partition
143 setUpLeadershipService(1);
144 replay(leadershipService);
145
146 Key myKey = new ControllableHashKey(0);
147 Key notMyKey = new ControllableHashKey(1);
148
149 assertTrue(partitionManager.isMine(myKey));
150 assertFalse(partitionManager.isMine(notMyKey));
151
152 // Make us the owner of 4 partitions now
153 reset(leadershipService);
154 setUpLeadershipService(4);
155 replay(leadershipService);
156
157 assertTrue(partitionManager.isMine(myKey));
158 // notMyKey is now my key because because we're in control of that
159 // partition now
160 assertTrue(partitionManager.isMine(notMyKey));
161
162 assertFalse(partitionManager.isMine(new ControllableHashKey(4)));
163 }
164
165 /**
166 * Tests sending in LeadershipServiceEvents in the case when we have
167 * too many partitions. The event will trigger the partition manager to
168 * reassess how many partitions it has and relinquish some.
169 */
170 @Test
171 public void testRelinquish() {
172 // We have all the partitions so we'll need to relinquish some
173 setUpLeadershipService(PartitionManager.NUM_PARTITIONS);
174
175 leadershipService.withdraw(anyString());
176 expectLastCall().times(7);
177
178 replay(leadershipService);
179
180 partitionManager.activate();
181 // Send in the event
182 leaderListener.event(event);
183
184 verify(leadershipService);
185 }
186
187 /**
188 * Tests sending in LeadershipServiceEvents in the case when we have the
189 * right amount or too many partitions. These events will not trigger any
190 * partition reassignments.
191 */
192 @Test
193 public void testNoRelinquish() {
194 // Partitions are already perfectly balanced among the two active instances
195 setUpLeadershipService(PartitionManager.NUM_PARTITIONS / 2);
196 replay(leadershipService);
197
198 partitionManager.activate();
199
200 // Send in the event
201 leaderListener.event(event);
202
203 verify(leadershipService);
204
205 reset(leadershipService);
206 // We have a smaller share than we should
207 setUpLeadershipService(PartitionManager.NUM_PARTITIONS / 2 - 1);
208 replay(leadershipService);
209
210 // Send in the event
211 leaderListener.event(event);
212
213 verify(leadershipService);
214 }
215
216 /**
217 * LeadershipService that allows us to grab a reference to
218 * PartitionManager's LeadershipEventListener.
219 */
220 public class TestLeadershipService extends LeadershipServiceAdapter {
221 @Override
222 public void addListener(LeadershipEventListener listener) {
223 leaderListener = listener;
224 }
225 }
226
227 /**
228 * ClusterService set up with a very simple cluster - 3 nodes, one is the
229 * current node, one is a different active node, and one is an inactive node.
230 */
231 private class TestClusterService extends ClusterServiceAdapter {
232
233 private final ControllerNode self =
234 new DefaultControllerNode(MY_NODE_ID, IpAddress.valueOf(1));
235 private final ControllerNode otherNode =
236 new DefaultControllerNode(OTHER_NODE_ID, IpAddress.valueOf(2));
237 private final ControllerNode inactiveNode =
238 new DefaultControllerNode(INACTIVE_NODE_ID, IpAddress.valueOf(3));
239
240 Set<ControllerNode> nodes;
241
242 public TestClusterService() {
243 nodes = new HashSet<>();
244 nodes.add(self);
245 nodes.add(otherNode);
246 nodes.add(inactiveNode);
247 }
248
249 @Override
250 public ControllerNode getLocalNode() {
251 return self;
252 }
253
254 @Override
255 public Set<ControllerNode> getNodes() {
256 return nodes;
257 }
258
259 @Override
260 public ControllerNode getNode(NodeId nodeId) {
261 return nodes.stream()
262 .filter(c -> c.id().equals(nodeId))
263 .findFirst()
264 .get();
265 }
266
267 @Override
268 public ControllerNode.State getState(NodeId nodeId) {
269 return nodeId.equals(INACTIVE_NODE_ID) ? ControllerNode.State.INACTIVE :
270 ControllerNode.State.ACTIVE;
271 }
272 }
273
274 /**
275 * A key that always hashes to a value provided to the constructor. This
276 * allows us to control the hash of the key for unit tests.
277 */
278 private class ControllableHashKey extends Key {
279
280 protected ControllableHashKey(long hash) {
281 super(hash);
282 }
283
284 @Override
285 public int hashCode() {
286 return Objects.hash(hash());
287 }
288
289 @Override
290 public boolean equals(Object obj) {
291 if (!(obj instanceof ControllableHashKey)) {
292 return false;
293 }
294
295 ControllableHashKey that = (ControllableHashKey) obj;
296
297 return Objects.equals(this.hash(), that.hash());
298 }
299 }
300}