Jonathan Hart
Committed by Gerrit Code Review

Unit tests for PartitionManager

Change-Id: I721ed6489ce19cb78ce9e2f150dfed90882f3b0e
1 +/*
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 + */
16 +package org.onosproject.cluster;
17 +
18 +import java.util.Map;
19 +import java.util.Set;
20 +
21 +/**
22 + * Test adapter for leadership service.
23 + */
24 +public class LeadershipServiceAdapter implements LeadershipService {
25 +
26 + @Override
27 + public NodeId getLeader(String path) {
28 + return null;
29 + }
30 +
31 + @Override
32 + public Leadership getLeadership(String path) {
33 + return null;
34 + }
35 +
36 + @Override
37 + public Set<String> ownedTopics(NodeId nodeId) {
38 + return null;
39 + }
40 +
41 + @Override
42 + public void runForLeadership(String path) {
43 +
44 + }
45 +
46 + @Override
47 + public void withdraw(String path) {
48 +
49 + }
50 +
51 + @Override
52 + public Map<String, Leadership> getLeaderBoard() {
53 + return null;
54 + }
55 +
56 + @Override
57 + public void addListener(LeadershipEventListener listener) {
58 +
59 + }
60 +
61 + @Override
62 + public void removeListener(LeadershipEventListener listener) {
63 +
64 + }
65 +}
...@@ -57,7 +57,7 @@ public class PartitionManager implements PartitionService { ...@@ -57,7 +57,7 @@ public class PartitionManager implements PartitionService {
57 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) 57 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
58 protected ClusterService clusterService; 58 protected ClusterService clusterService;
59 59
60 - private static final int NUM_PARTITIONS = 14; 60 + static final int NUM_PARTITIONS = 14;
61 private static final int BACKOFF_TIME = 2; 61 private static final int BACKOFF_TIME = 2;
62 private static final int CHECK_PERIOD = 10; 62 private static final int CHECK_PERIOD = 10;
63 63
...@@ -90,6 +90,17 @@ public class PartitionManager implements PartitionService { ...@@ -90,6 +90,17 @@ public class PartitionManager implements PartitionService {
90 clusterService.removeListener(clusterListener); 90 clusterService.removeListener(clusterListener);
91 } 91 }
92 92
93 + /**
94 + * Sets the specified executor to be used for scheduling background tasks.
95 + *
96 + * @param executor scheduled executor service for background tasks
97 + * @return this PartitionManager
98 + */
99 + public PartitionManager withScheduledExecutor(ScheduledExecutorService executor) {
100 + this.executor = executor;
101 + return this;
102 + }
103 +
93 private String getPartitionPath(int i) { 104 private String getPartitionPath(int i) {
94 return ELECTION_PREFIX + i; 105 return ELECTION_PREFIX + i;
95 } 106 }
......
1 +/*
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 + */
16 +package org.onosproject.store.intent.impl;
17 +
18 +import org.junit.Before;
19 +import org.junit.Test;
20 +import org.onlab.junit.NullScheduledExecutor;
21 +import org.onlab.packet.IpAddress;
22 +import org.onosproject.cluster.ClusterServiceAdapter;
23 +import org.onosproject.cluster.ControllerNode;
24 +import org.onosproject.cluster.DefaultControllerNode;
25 +import org.onosproject.cluster.Leadership;
26 +import org.onosproject.cluster.LeadershipEvent;
27 +import org.onosproject.cluster.LeadershipEventListener;
28 +import org.onosproject.cluster.LeadershipService;
29 +import org.onosproject.cluster.LeadershipServiceAdapter;
30 +import org.onosproject.cluster.NodeId;
31 +import org.onosproject.net.intent.Key;
32 +
33 +import java.util.HashMap;
34 +import java.util.HashSet;
35 +import java.util.Map;
36 +import java.util.Objects;
37 +import java.util.Set;
38 +
39 +import static junit.framework.TestCase.assertFalse;
40 +import static org.easymock.EasyMock.anyObject;
41 +import static org.easymock.EasyMock.anyString;
42 +import static org.easymock.EasyMock.createMock;
43 +import static org.easymock.EasyMock.expect;
44 +import static org.easymock.EasyMock.expectLastCall;
45 +import static org.easymock.EasyMock.replay;
46 +import static org.easymock.EasyMock.reset;
47 +import static org.easymock.EasyMock.verify;
48 +import static org.junit.Assert.assertTrue;
49 +
50 +/**
51 + * Unit tests for the PartitionManager class.
52 + */
53 +public 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 +}
1 +/*
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 + */
16 +package org.onlab.junit;
17 +
18 +import java.util.Collection;
19 +import java.util.List;
20 +import java.util.concurrent.Callable;
21 +import java.util.concurrent.ExecutionException;
22 +import java.util.concurrent.Future;
23 +import java.util.concurrent.ScheduledExecutorService;
24 +import java.util.concurrent.ScheduledFuture;
25 +import java.util.concurrent.TimeUnit;
26 +import java.util.concurrent.TimeoutException;
27 +
28 +/**
29 + * A scheduled executor service that does not do any of the work scheduled to it.
30 + * <p>
31 + * This is useful for testing when you want to disable a background scheduled
32 + * task.
33 + * </p>
34 + */
35 +public class NullScheduledExecutor implements ScheduledExecutorService {
36 + @Override
37 + public ScheduledFuture<?> schedule(Runnable command, long delay,
38 + TimeUnit unit) {
39 + return null;
40 + }
41 +
42 + @Override
43 + public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay,
44 + TimeUnit unit) {
45 + return null;
46 + }
47 +
48 + @Override
49 + public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
50 + long initialDelay,
51 + long period, TimeUnit unit) {
52 + return null;
53 + }
54 +
55 + @Override
56 + public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
57 + long initialDelay,
58 + long delay,
59 + TimeUnit unit) {
60 + return null;
61 + }
62 +
63 + @Override
64 + public void shutdown() {
65 +
66 + }
67 +
68 + @Override
69 + public List<Runnable> shutdownNow() {
70 + return null;
71 + }
72 +
73 + @Override
74 + public boolean isShutdown() {
75 + return false;
76 + }
77 +
78 + @Override
79 + public boolean isTerminated() {
80 + return false;
81 + }
82 +
83 + @Override
84 + public boolean awaitTermination(long timeout, TimeUnit unit)
85 + throws InterruptedException {
86 + return false;
87 + }
88 +
89 + @Override
90 + public <T> Future<T> submit(Callable<T> task) {
91 + return null;
92 + }
93 +
94 + @Override
95 + public <T> Future<T> submit(Runnable task, T result) {
96 + return null;
97 + }
98 +
99 + @Override
100 + public Future<?> submit(Runnable task) {
101 + return null;
102 + }
103 +
104 + @Override
105 + public <T> List<Future<T>> invokeAll(
106 + Collection<? extends Callable<T>> tasks)
107 + throws InterruptedException {
108 + return null;
109 + }
110 +
111 + @Override
112 + public <T> List<Future<T>> invokeAll(
113 + Collection<? extends Callable<T>> tasks, long timeout,
114 + TimeUnit unit) throws InterruptedException {
115 + return null;
116 + }
117 +
118 + @Override
119 + public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
120 + throws InterruptedException, ExecutionException {
121 + return null;
122 + }
123 +
124 + @Override
125 + public <T> T invokeAny(Collection<? extends Callable<T>> tasks,
126 + long timeout, TimeUnit unit)
127 + throws InterruptedException, ExecutionException, TimeoutException {
128 + return null;
129 + }
130 +
131 + @Override
132 + public void execute(Runnable command) {
133 +
134 + }
135 +}