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