Committed by
Gerrit Code Review
[Goldeneye] ONOS-4017: Mastership service considers Region information when determining mastership.
Change-Id: I6c79239f2e071d865bf04e4d9d790ca9b2d04694
Showing
3 changed files
with
390 additions
and
14 deletions
... | @@ -67,6 +67,14 @@ | ... | @@ -67,6 +67,14 @@ |
67 | 67 | ||
68 | <dependency> | 68 | <dependency> |
69 | <groupId>org.onosproject</groupId> | 69 | <groupId>org.onosproject</groupId> |
70 | + <artifactId>onos-core-dist</artifactId> | ||
71 | + <scope>test</scope> | ||
72 | + <classifier>tests</classifier> | ||
73 | + <version>${project.version}</version> | ||
74 | + </dependency> | ||
75 | + | ||
76 | + <dependency> | ||
77 | + <groupId>org.onosproject</groupId> | ||
70 | <artifactId>onos-incubator-api</artifactId> | 78 | <artifactId>onos-incubator-api</artifactId> |
71 | <scope>test</scope> | 79 | <scope>test</scope> |
72 | <classifier>tests</classifier> | 80 | <classifier>tests</classifier> | ... | ... |
... | @@ -18,6 +18,7 @@ package org.onosproject.cluster.impl; | ... | @@ -18,6 +18,7 @@ package org.onosproject.cluster.impl; |
18 | import com.codahale.metrics.Timer; | 18 | import com.codahale.metrics.Timer; |
19 | import com.codahale.metrics.Timer.Context; | 19 | import com.codahale.metrics.Timer.Context; |
20 | import com.google.common.collect.Lists; | 20 | import com.google.common.collect.Lists; |
21 | +import com.google.common.collect.Sets; | ||
21 | import com.google.common.util.concurrent.Futures; | 22 | import com.google.common.util.concurrent.Futures; |
22 | import org.apache.felix.scr.annotations.Activate; | 23 | import org.apache.felix.scr.annotations.Activate; |
23 | import org.apache.felix.scr.annotations.Component; | 24 | import org.apache.felix.scr.annotations.Component; |
... | @@ -42,9 +43,13 @@ import org.onosproject.mastership.MastershipTerm; | ... | @@ -42,9 +43,13 @@ import org.onosproject.mastership.MastershipTerm; |
42 | import org.onosproject.mastership.MastershipTermService; | 43 | import org.onosproject.mastership.MastershipTermService; |
43 | import org.onosproject.net.DeviceId; | 44 | import org.onosproject.net.DeviceId; |
44 | import org.onosproject.net.MastershipRole; | 45 | import org.onosproject.net.MastershipRole; |
46 | +import org.onosproject.net.region.Region; | ||
47 | +import org.onosproject.net.region.RegionService; | ||
45 | import org.slf4j.Logger; | 48 | import org.slf4j.Logger; |
46 | 49 | ||
50 | +import java.util.ArrayList; | ||
47 | import java.util.Collection; | 51 | import java.util.Collection; |
52 | +import java.util.Collections; | ||
48 | import java.util.HashMap; | 53 | import java.util.HashMap; |
49 | import java.util.HashSet; | 54 | import java.util.HashSet; |
50 | import java.util.Iterator; | 55 | import java.util.Iterator; |
... | @@ -89,8 +94,12 @@ public class MastershipManager | ... | @@ -89,8 +94,12 @@ public class MastershipManager |
89 | @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) | 94 | @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
90 | protected MetricsService metricsService; | 95 | protected MetricsService metricsService; |
91 | 96 | ||
97 | + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) | ||
98 | + protected RegionService regionService; | ||
99 | + | ||
92 | private NodeId localNodeId; | 100 | private NodeId localNodeId; |
93 | private Timer requestRoleTimer; | 101 | private Timer requestRoleTimer; |
102 | + public boolean useRegionForBalanceRoles; | ||
94 | 103 | ||
95 | @Activate | 104 | @Activate |
96 | public void activate() { | 105 | public void activate() { |
... | @@ -212,6 +221,28 @@ public class MastershipManager | ... | @@ -212,6 +221,28 @@ public class MastershipManager |
212 | } | 221 | } |
213 | } | 222 | } |
214 | 223 | ||
224 | + if (useRegionForBalanceRoles && balanceRolesUsingRegions(controllerDevices)) { | ||
225 | + return; | ||
226 | + } | ||
227 | + | ||
228 | + // Now re-balance the buckets until they are roughly even. | ||
229 | + List<CompletableFuture<Void>> balanceBucketsFutures = balanceControllerNodes(controllerDevices, deviceCount); | ||
230 | + | ||
231 | + CompletableFuture<Void> balanceRolesFuture = CompletableFuture.allOf( | ||
232 | + balanceBucketsFutures.toArray(new CompletableFuture[balanceBucketsFutures.size()])); | ||
233 | + | ||
234 | + Futures.getUnchecked(balanceRolesFuture); | ||
235 | + } | ||
236 | + | ||
237 | + /** | ||
238 | + * Balances the nodes specified in controllerDevices. | ||
239 | + * | ||
240 | + * @param controllerDevices controller nodes to devices map | ||
241 | + * @param deviceCount number of devices mastered by controller nodes | ||
242 | + * @return list of setRole futures for "moved" devices | ||
243 | + */ | ||
244 | + private List<CompletableFuture<Void>> balanceControllerNodes( | ||
245 | + Map<ControllerNode, Set<DeviceId>> controllerDevices, int deviceCount) { | ||
215 | // Now re-balance the buckets until they are roughly even. | 246 | // Now re-balance the buckets until they are roughly even. |
216 | List<CompletableFuture<Void>> balanceBucketsFutures = Lists.newLinkedList(); | 247 | List<CompletableFuture<Void>> balanceBucketsFutures = Lists.newLinkedList(); |
217 | int rounds = controllerDevices.keySet().size(); | 248 | int rounds = controllerDevices.keySet().size(); |
... | @@ -221,12 +252,16 @@ public class MastershipManager | ... | @@ -221,12 +252,16 @@ public class MastershipManager |
221 | ControllerNode largest = findBucket(false, controllerDevices); | 252 | ControllerNode largest = findBucket(false, controllerDevices); |
222 | balanceBucketsFutures.add(balanceBuckets(smallest, largest, controllerDevices, deviceCount)); | 253 | balanceBucketsFutures.add(balanceBuckets(smallest, largest, controllerDevices, deviceCount)); |
223 | } | 254 | } |
224 | - CompletableFuture<Void> balanceRolesFuture = CompletableFuture.allOf( | 255 | + return balanceBucketsFutures; |
225 | - balanceBucketsFutures.toArray(new CompletableFuture[balanceBucketsFutures.size()])); | ||
226 | - | ||
227 | - Futures.getUnchecked(balanceRolesFuture); | ||
228 | } | 256 | } |
229 | 257 | ||
258 | + /** | ||
259 | + * Finds node with the minimum/maximum devices from a list of nodes. | ||
260 | + * | ||
261 | + * @param min true: minimum, false: maximum | ||
262 | + * @param controllerDevices controller nodes to devices map | ||
263 | + * @return controller node with minimum/maximum devices | ||
264 | + */ | ||
230 | private ControllerNode findBucket(boolean min, | 265 | private ControllerNode findBucket(boolean min, |
231 | Map<ControllerNode, Set<DeviceId>> controllerDevices) { | 266 | Map<ControllerNode, Set<DeviceId>> controllerDevices) { |
232 | int xSize = min ? Integer.MAX_VALUE : -1; | 267 | int xSize = min ? Integer.MAX_VALUE : -1; |
... | @@ -241,6 +276,15 @@ public class MastershipManager | ... | @@ -241,6 +276,15 @@ public class MastershipManager |
241 | return xNode; | 276 | return xNode; |
242 | } | 277 | } |
243 | 278 | ||
279 | + /** | ||
280 | + * Balance the node buckets by moving devices from largest to smallest node. | ||
281 | + * | ||
282 | + * @param smallest node that is master of the smallest number of devices | ||
283 | + * @param largest node that is master of the largest number of devices | ||
284 | + * @param controllerDevices controller nodes to devices map | ||
285 | + * @param deviceCount number of devices mastered by controller nodes | ||
286 | + * @return list of setRole futures for "moved" devices | ||
287 | + */ | ||
244 | private CompletableFuture<Void> balanceBuckets(ControllerNode smallest, ControllerNode largest, | 288 | private CompletableFuture<Void> balanceBuckets(ControllerNode smallest, ControllerNode largest, |
245 | Map<ControllerNode, Set<DeviceId>> controllerDevices, | 289 | Map<ControllerNode, Set<DeviceId>> controllerDevices, |
246 | int deviceCount) { | 290 | int deviceCount) { |
... | @@ -272,6 +316,149 @@ public class MastershipManager | ... | @@ -272,6 +316,149 @@ public class MastershipManager |
272 | return CompletableFuture.allOf(setRoleFutures.toArray(new CompletableFuture[setRoleFutures.size()])); | 316 | return CompletableFuture.allOf(setRoleFutures.toArray(new CompletableFuture[setRoleFutures.size()])); |
273 | } | 317 | } |
274 | 318 | ||
319 | + /** | ||
320 | + * Balances the nodes considering Region information. | ||
321 | + * | ||
322 | + * @param allControllerDevices controller nodes to devices map | ||
323 | + * @return true: nodes balanced; false: nodes not balanced | ||
324 | + */ | ||
325 | + private boolean balanceRolesUsingRegions(Map<ControllerNode, Set<DeviceId>> allControllerDevices) { | ||
326 | + Set<Region> regions = regionService.getRegions(); | ||
327 | + if (regions.isEmpty()) { | ||
328 | + return false; // no balancing was done using regions. | ||
329 | + } | ||
330 | + | ||
331 | + // handle nodes belonging to regions | ||
332 | + Set<ControllerNode> nodesInRegions = Sets.newHashSet(); | ||
333 | + for (Region region : regions) { | ||
334 | + Map<ControllerNode, Set<DeviceId>> activeRegionControllers = | ||
335 | + balanceRolesInRegion(region, allControllerDevices); | ||
336 | + nodesInRegions.addAll(activeRegionControllers.keySet()); | ||
337 | + } | ||
338 | + | ||
339 | + // handle nodes not belonging to any region | ||
340 | + Set<ControllerNode> nodesNotInRegions = Sets.difference(allControllerDevices.keySet(), nodesInRegions); | ||
341 | + if (!nodesNotInRegions.isEmpty()) { | ||
342 | + int deviceCount = 0; | ||
343 | + Map<ControllerNode, Set<DeviceId>> controllerDevicesNotInRegions = new HashMap<>(); | ||
344 | + for (ControllerNode controllerNode: nodesNotInRegions) { | ||
345 | + controllerDevicesNotInRegions.put(controllerNode, allControllerDevices.get(controllerNode)); | ||
346 | + deviceCount += allControllerDevices.get(controllerNode).size(); | ||
347 | + } | ||
348 | + // Now re-balance the buckets until they are roughly even. | ||
349 | + List<CompletableFuture<Void>> balanceBucketsFutures = | ||
350 | + balanceControllerNodes(controllerDevicesNotInRegions, deviceCount); | ||
351 | + | ||
352 | + CompletableFuture<Void> balanceRolesFuture = CompletableFuture.allOf( | ||
353 | + balanceBucketsFutures.toArray(new CompletableFuture[balanceBucketsFutures.size()])); | ||
354 | + | ||
355 | + Futures.getUnchecked(balanceRolesFuture); | ||
356 | + } | ||
357 | + return true; // balancing was done using regions. | ||
358 | + } | ||
359 | + | ||
360 | + /** | ||
361 | + * Balances the nodes in specified region. | ||
362 | + * | ||
363 | + * @param region region in which nodes are to be balanced | ||
364 | + * @param allControllerDevices controller nodes to devices map | ||
365 | + * @return controller nodes that were balanced | ||
366 | + */ | ||
367 | + private Map<ControllerNode, Set<DeviceId>> balanceRolesInRegion(Region region, | ||
368 | + Map<ControllerNode, Set<DeviceId>> allControllerDevices) { | ||
369 | + | ||
370 | + // retrieve all devices associated with specified region | ||
371 | + Set<DeviceId> devicesInRegion = regionService.getRegionDevices(region.id()); | ||
372 | + log.info("Region {} has {} devices.", region.id(), devicesInRegion.size()); | ||
373 | + if (devicesInRegion.isEmpty()) { | ||
374 | + return new HashMap<>(); // no devices in this region, so nothing to balance. | ||
375 | + } | ||
376 | + | ||
377 | + List<Set<NodeId>> mastersList = region.masters(); | ||
378 | + log.info("Region {} has {} sets of masters.", region.id(), mastersList.size()); | ||
379 | + if (mastersList.isEmpty()) { | ||
380 | + // TODO handle devices that belong to a region, which has no masters defined | ||
381 | + return new HashMap<>(); // for now just leave devices alone | ||
382 | + } | ||
383 | + | ||
384 | + // get the region's preferred set of masters | ||
385 | + Set<DeviceId> devicesInMasters = Sets.newHashSet(); | ||
386 | + Map<ControllerNode, Set<DeviceId>> regionalControllerDevices = | ||
387 | + getRegionsPreferredMasters(region, devicesInMasters, allControllerDevices); | ||
388 | + | ||
389 | + // Now re-balance the buckets until they are roughly even. | ||
390 | + List<CompletableFuture<Void>> balanceBucketsFutures = | ||
391 | + balanceControllerNodes(regionalControllerDevices, devicesInMasters.size()); | ||
392 | + | ||
393 | + // handle devices that are not currently mastered by the master node set | ||
394 | + Set<DeviceId> devicesNotMasteredWithControllers = Sets.difference(devicesInRegion, devicesInMasters); | ||
395 | + if (!devicesNotMasteredWithControllers.isEmpty()) { | ||
396 | + // active controllers in master node set are already balanced, just | ||
397 | + // assign device mastership in sequence | ||
398 | + List<ControllerNode> sorted = new ArrayList<>(regionalControllerDevices.keySet()); | ||
399 | + Collections.sort(sorted, (o1, o2) -> | ||
400 | + ((Integer) (regionalControllerDevices.get(o1)).size()) | ||
401 | + .compareTo((Integer) (regionalControllerDevices.get(o2)).size())); | ||
402 | + int deviceIndex = 0; | ||
403 | + for (DeviceId deviceId : devicesNotMasteredWithControllers) { | ||
404 | + ControllerNode cnode = sorted.get(deviceIndex % sorted.size()); | ||
405 | + balanceBucketsFutures.add(setRole(cnode.id(), deviceId, MASTER)); | ||
406 | + regionalControllerDevices.get(cnode).add(deviceId); | ||
407 | + deviceIndex++; | ||
408 | + } | ||
409 | + } | ||
410 | + | ||
411 | + CompletableFuture<Void> balanceRolesFuture = CompletableFuture.allOf( | ||
412 | + balanceBucketsFutures.toArray(new CompletableFuture[balanceBucketsFutures.size()])); | ||
413 | + | ||
414 | + Futures.getUnchecked(balanceRolesFuture); | ||
415 | + | ||
416 | + // update the map before returning | ||
417 | + regionalControllerDevices.forEach((controllerNode, deviceIds) -> { | ||
418 | + regionalControllerDevices.put(controllerNode, new HashSet<>(getDevicesOf(controllerNode.id()))); | ||
419 | + }); | ||
420 | + | ||
421 | + return regionalControllerDevices; | ||
422 | + } | ||
423 | + | ||
424 | + /** | ||
425 | + * Get region's preferred set of master nodes - the first master node set that has at | ||
426 | + * least one active node. | ||
427 | + * | ||
428 | + * @param region region for which preferred set of master nodes is requested | ||
429 | + * @param devicesInMasters device set to track devices in preferred set of master nodes | ||
430 | + * @param allControllerDevices controller nodes to devices map | ||
431 | + * @return region's preferred master nodes (and devices that use them as masters) | ||
432 | + */ | ||
433 | + private Map<ControllerNode, Set<DeviceId>> getRegionsPreferredMasters(Region region, | ||
434 | + Set<DeviceId> devicesInMasters, | ||
435 | + Map<ControllerNode, Set<DeviceId>> allControllerDevices) { | ||
436 | + Map<ControllerNode, Set<DeviceId>> regionalControllerDevices = new HashMap<>(); | ||
437 | + int listIndex = 0; | ||
438 | + for (Set<NodeId> masterSet: region.masters()) { | ||
439 | + log.info("Region {} masters set {} has {} nodes.", | ||
440 | + region.id(), listIndex, masterSet.size()); | ||
441 | + if (masterSet.isEmpty()) { // nothing on this level | ||
442 | + listIndex++; | ||
443 | + continue; | ||
444 | + } | ||
445 | + // Create buckets reflecting current ownership. | ||
446 | + for (NodeId nodeId : masterSet) { | ||
447 | + if (clusterService.getState(nodeId) == ACTIVE) { | ||
448 | + ControllerNode controllerNode = clusterService.getNode(nodeId); | ||
449 | + Set<DeviceId> devicesOf = new HashSet<>(allControllerDevices.get(controllerNode)); | ||
450 | + regionalControllerDevices.put(controllerNode, devicesOf); | ||
451 | + devicesInMasters.addAll(devicesOf); | ||
452 | + log.info("Active Node {} has {} devices.", nodeId, devicesOf.size()); | ||
453 | + } | ||
454 | + } | ||
455 | + if (!regionalControllerDevices.isEmpty()) { | ||
456 | + break; // now have a set of >0 active controllers | ||
457 | + } | ||
458 | + listIndex++; // keep on looking | ||
459 | + } | ||
460 | + return regionalControllerDevices; | ||
461 | + } | ||
275 | 462 | ||
276 | public class InternalDelegate implements MastershipStoreDelegate { | 463 | public class InternalDelegate implements MastershipStoreDelegate { |
277 | @Override | 464 | @Override | ... | ... |
... | @@ -15,14 +15,18 @@ | ... | @@ -15,14 +15,18 @@ |
15 | */ | 15 | */ |
16 | package org.onosproject.cluster.impl; | 16 | package org.onosproject.cluster.impl; |
17 | 17 | ||
18 | +import java.util.List; | ||
18 | import java.util.Set; | 19 | import java.util.Set; |
20 | +import java.util.function.Consumer; | ||
19 | 21 | ||
22 | +import com.google.common.collect.ImmutableList; | ||
23 | +import com.google.common.collect.ImmutableSet; | ||
20 | import org.junit.After; | 24 | import org.junit.After; |
21 | import org.junit.Before; | 25 | import org.junit.Before; |
22 | import org.junit.Test; | 26 | import org.junit.Test; |
27 | +import org.onlab.junit.TestUtils; | ||
23 | import org.onlab.packet.IpAddress; | 28 | import org.onlab.packet.IpAddress; |
24 | import org.onosproject.cluster.ClusterService; | 29 | import org.onosproject.cluster.ClusterService; |
25 | -import org.onosproject.cluster.ClusterServiceAdapter; | ||
26 | import org.onosproject.cluster.ControllerNode; | 30 | import org.onosproject.cluster.ControllerNode; |
27 | import org.onosproject.cluster.DefaultControllerNode; | 31 | import org.onosproject.cluster.DefaultControllerNode; |
28 | import org.onosproject.cluster.NodeId; | 32 | import org.onosproject.cluster.NodeId; |
... | @@ -31,17 +35,24 @@ import org.onosproject.mastership.MastershipService; | ... | @@ -31,17 +35,24 @@ import org.onosproject.mastership.MastershipService; |
31 | import org.onosproject.mastership.MastershipStore; | 35 | import org.onosproject.mastership.MastershipStore; |
32 | import org.onosproject.mastership.MastershipTermService; | 36 | import org.onosproject.mastership.MastershipTermService; |
33 | import org.onosproject.net.DeviceId; | 37 | import org.onosproject.net.DeviceId; |
38 | +import org.onosproject.net.region.Region; | ||
39 | +import org.onosproject.net.region.RegionId; | ||
40 | +import org.onosproject.net.region.RegionStore; | ||
41 | +import org.onosproject.net.region.impl.RegionManager; | ||
42 | +import org.onosproject.store.cluster.StaticClusterService; | ||
43 | +import org.onosproject.store.region.impl.DistributedRegionStore; | ||
44 | +import org.onosproject.store.service.TestStorageService; | ||
34 | import org.onosproject.store.trivial.SimpleMastershipStore; | 45 | import org.onosproject.store.trivial.SimpleMastershipStore; |
35 | 46 | ||
36 | import com.google.common.collect.Sets; | 47 | import com.google.common.collect.Sets; |
37 | import com.google.common.util.concurrent.Futures; | 48 | import com.google.common.util.concurrent.Futures; |
38 | 49 | ||
39 | -import static org.junit.Assert.assertEquals; | 50 | +import static org.junit.Assert.*; |
40 | -import static org.junit.Assert.assertNull; | ||
41 | import static org.onosproject.net.MastershipRole.MASTER; | 51 | import static org.onosproject.net.MastershipRole.MASTER; |
42 | import static org.onosproject.net.MastershipRole.NONE; | 52 | import static org.onosproject.net.MastershipRole.NONE; |
43 | import static org.onosproject.net.MastershipRole.STANDBY; | 53 | import static org.onosproject.net.MastershipRole.STANDBY; |
44 | import static org.onosproject.net.NetTestTools.injectEventDispatcher; | 54 | import static org.onosproject.net.NetTestTools.injectEventDispatcher; |
55 | +import static org.onosproject.net.region.Region.Type.METRO; | ||
45 | 56 | ||
46 | /** | 57 | /** |
47 | * Test codifying the mastership service contracts. | 58 | * Test codifying the mastership service contracts. |
... | @@ -54,16 +65,47 @@ public class MastershipManagerTest { | ... | @@ -54,16 +65,47 @@ public class MastershipManagerTest { |
54 | private static final DeviceId DEV_MASTER = DeviceId.deviceId("of:1"); | 65 | private static final DeviceId DEV_MASTER = DeviceId.deviceId("of:1"); |
55 | private static final DeviceId DEV_OTHER = DeviceId.deviceId("of:2"); | 66 | private static final DeviceId DEV_OTHER = DeviceId.deviceId("of:2"); |
56 | 67 | ||
68 | + private static final RegionId RID1 = RegionId.regionId("r1"); | ||
69 | + private static final RegionId RID2 = RegionId.regionId("r2"); | ||
70 | + private static final DeviceId DID1 = DeviceId.deviceId("foo:d1"); | ||
71 | + private static final DeviceId DID2 = DeviceId.deviceId("foo:d2"); | ||
72 | + private static final DeviceId DID3 = DeviceId.deviceId("foo:d3"); | ||
73 | + private static final NodeId NID1 = NodeId.nodeId("n1"); | ||
74 | + private static final NodeId NID2 = NodeId.nodeId("n2"); | ||
75 | + private static final NodeId NID3 = NodeId.nodeId("n3"); | ||
76 | + private static final NodeId NID4 = NodeId.nodeId("n4"); | ||
77 | + private static final ControllerNode CNODE1 = | ||
78 | + new DefaultControllerNode(NID1, IpAddress.valueOf("127.0.1.1")); | ||
79 | + private static final ControllerNode CNODE2 = | ||
80 | + new DefaultControllerNode(NID2, IpAddress.valueOf("127.0.1.2")); | ||
81 | + private static final ControllerNode CNODE3 = | ||
82 | + new DefaultControllerNode(NID3, IpAddress.valueOf("127.0.1.3")); | ||
83 | + private static final ControllerNode CNODE4 = | ||
84 | + new DefaultControllerNode(NID4, IpAddress.valueOf("127.0.1.4")); | ||
85 | + | ||
86 | + | ||
57 | private MastershipManager mgr; | 87 | private MastershipManager mgr; |
58 | protected MastershipService service; | 88 | protected MastershipService service; |
89 | + private TestRegionManager regionManager; | ||
90 | + private RegionStore regionStore; | ||
91 | + private TestClusterService testClusterService; | ||
59 | 92 | ||
60 | @Before | 93 | @Before |
61 | - public void setUp() { | 94 | + public void setUp() throws Exception { |
62 | mgr = new MastershipManager(); | 95 | mgr = new MastershipManager(); |
63 | service = mgr; | 96 | service = mgr; |
64 | injectEventDispatcher(mgr, new TestEventDispatcher()); | 97 | injectEventDispatcher(mgr, new TestEventDispatcher()); |
65 | - mgr.clusterService = new TestClusterService(); | 98 | + testClusterService = new TestClusterService(); |
99 | + mgr.clusterService = testClusterService; | ||
66 | mgr.store = new TestSimpleMastershipStore(mgr.clusterService); | 100 | mgr.store = new TestSimpleMastershipStore(mgr.clusterService); |
101 | + regionStore = new DistributedRegionStore(); | ||
102 | + TestUtils.setField(regionStore, "storageService", new TestStorageService()); | ||
103 | + TestUtils.callMethod(regionStore, "activate", | ||
104 | + new Class<?>[] {}); | ||
105 | + regionManager = new TestRegionManager(); | ||
106 | + TestUtils.setField(regionManager, "store", regionStore); | ||
107 | + regionManager.activate(); | ||
108 | + mgr.regionService = regionManager; | ||
67 | mgr.activate(); | 109 | mgr.activate(); |
68 | } | 110 | } |
69 | 111 | ||
... | @@ -72,6 +114,8 @@ public class MastershipManagerTest { | ... | @@ -72,6 +114,8 @@ public class MastershipManagerTest { |
72 | mgr.deactivate(); | 114 | mgr.deactivate(); |
73 | mgr.clusterService = null; | 115 | mgr.clusterService = null; |
74 | injectEventDispatcher(mgr, null); | 116 | injectEventDispatcher(mgr, null); |
117 | + regionManager.deactivate(); | ||
118 | + mgr.regionService = null; | ||
75 | mgr.store = null; | 119 | mgr.store = null; |
76 | } | 120 | } |
77 | 121 | ||
... | @@ -154,7 +198,139 @@ public class MastershipManagerTest { | ... | @@ -154,7 +198,139 @@ public class MastershipManagerTest { |
154 | assertEquals("inconsistent terms: ", 3, ts.getMastershipTerm(DEV_MASTER).termNumber()); | 198 | assertEquals("inconsistent terms: ", 3, ts.getMastershipTerm(DEV_MASTER).termNumber()); |
155 | } | 199 | } |
156 | 200 | ||
157 | - private final class TestClusterService extends ClusterServiceAdapter { | 201 | + @Test |
202 | + public void balanceWithRegion1() { | ||
203 | + //set up region - 2 sets of masters with 1 node in each | ||
204 | + Set<NodeId> masterSet1 = ImmutableSet.of(NID1); | ||
205 | + Set<NodeId> masterSet2 = ImmutableSet.of(NID2); | ||
206 | + List<Set<NodeId>> masters = ImmutableList.of(masterSet1, masterSet2); | ||
207 | + Region r = regionManager.createRegion(RID1, "R1", METRO, masters); | ||
208 | + regionManager.addDevices(RID1, ImmutableSet.of(DID1, DID2)); | ||
209 | + Set<DeviceId> deviceIds = regionManager.getRegionDevices(RID1); | ||
210 | + assertEquals("incorrect device count", 2, deviceIds.size()); | ||
211 | + | ||
212 | + testClusterService.put(CNODE1, ControllerNode.State.ACTIVE); | ||
213 | + testClusterService.put(CNODE2, ControllerNode.State.ACTIVE); | ||
214 | + | ||
215 | + //set master to non region nodes | ||
216 | + mgr.setRole(NID_LOCAL, DID1, MASTER); | ||
217 | + mgr.setRole(NID_LOCAL, DID2, MASTER); | ||
218 | + assertEquals("wrong local role:", MASTER, mgr.getLocalRole(DID1)); | ||
219 | + assertEquals("wrong local role:", MASTER, mgr.getLocalRole(DID2)); | ||
220 | + assertEquals("wrong master:", NID_LOCAL, mgr.getMasterFor(DID1)); | ||
221 | + assertEquals("wrong master:", NID_LOCAL, mgr.getMasterFor(DID2)); | ||
222 | + | ||
223 | + //do region balancing | ||
224 | + mgr.useRegionForBalanceRoles = true; | ||
225 | + mgr.balanceRoles(); | ||
226 | + assertEquals("wrong master:", NID1, mgr.getMasterFor(DID1)); | ||
227 | + assertEquals("wrong master:", NID1, mgr.getMasterFor(DID2)); | ||
228 | + | ||
229 | + // make N1 inactive | ||
230 | + testClusterService.put(CNODE1, ControllerNode.State.INACTIVE); | ||
231 | + mgr.balanceRoles(); | ||
232 | + assertEquals("wrong master:", NID2, mgr.getMasterFor(DID1)); | ||
233 | + assertEquals("wrong master:", NID2, mgr.getMasterFor(DID2)); | ||
234 | + | ||
235 | + } | ||
236 | + | ||
237 | + @Test | ||
238 | + public void balanceWithRegion2() { | ||
239 | + //set up region - 2 sets of masters with (3 nodes, 1 node) | ||
240 | + Set<NodeId> masterSet1 = ImmutableSet.of(NID1, NID3, NID4); | ||
241 | + Set<NodeId> masterSet2 = ImmutableSet.of(NID2); | ||
242 | + List<Set<NodeId>> masters = ImmutableList.of(masterSet1, masterSet2); | ||
243 | + Region r = regionManager.createRegion(RID1, "R1", METRO, masters); | ||
244 | + Set<DeviceId> deviceIdsOrig = ImmutableSet.of(DID1, DID2, DID3, DEV_OTHER); | ||
245 | + regionManager.addDevices(RID1, deviceIdsOrig); | ||
246 | + Set<DeviceId> deviceIds = regionManager.getRegionDevices(RID1); | ||
247 | + assertEquals("incorrect device count", deviceIdsOrig.size(), deviceIds.size()); | ||
248 | + assertEquals("incorrect devices in region", deviceIdsOrig, deviceIds); | ||
249 | + | ||
250 | + testClusterService.put(CNODE1, ControllerNode.State.ACTIVE); | ||
251 | + testClusterService.put(CNODE2, ControllerNode.State.ACTIVE); | ||
252 | + testClusterService.put(CNODE3, ControllerNode.State.ACTIVE); | ||
253 | + testClusterService.put(CNODE4, ControllerNode.State.ACTIVE); | ||
254 | + | ||
255 | + //set master to non region nodes | ||
256 | + deviceIdsOrig.forEach(deviceId1 -> mgr.setRole(NID_LOCAL, deviceId1, MASTER)); | ||
257 | + checkDeviceMasters(deviceIds, Sets.newHashSet(NID_LOCAL), deviceId -> | ||
258 | + assertEquals("wrong local role:", MASTER, mgr.getLocalRole(deviceId))); | ||
259 | + | ||
260 | + //do region balancing | ||
261 | + mgr.useRegionForBalanceRoles = true; | ||
262 | + mgr.balanceRoles(); | ||
263 | + Set<NodeId> expectedMasters = Sets.newHashSet(NID1, NID3, NID4); | ||
264 | + checkDeviceMasters(deviceIds, expectedMasters); | ||
265 | + | ||
266 | + // make N1 inactive | ||
267 | + testClusterService.put(CNODE1, ControllerNode.State.INACTIVE); | ||
268 | + expectedMasters.remove(NID1); | ||
269 | + mgr.balanceRoles(); | ||
270 | + checkDeviceMasters(deviceIds, expectedMasters); | ||
271 | + | ||
272 | + // make N4 inactive | ||
273 | + testClusterService.put(CNODE4, ControllerNode.State.INACTIVE); | ||
274 | + expectedMasters.remove(NID4); | ||
275 | + mgr.balanceRoles(); | ||
276 | + checkDeviceMasters(deviceIds, expectedMasters); | ||
277 | + | ||
278 | + // make N3 inactive | ||
279 | + testClusterService.put(CNODE3, ControllerNode.State.INACTIVE); | ||
280 | + expectedMasters = Sets.newHashSet(NID2); | ||
281 | + mgr.balanceRoles(); | ||
282 | + checkDeviceMasters(deviceIds, expectedMasters); | ||
283 | + | ||
284 | + // make N3 active | ||
285 | + testClusterService.put(CNODE3, ControllerNode.State.ACTIVE); | ||
286 | + expectedMasters = Sets.newHashSet(NID3); | ||
287 | + mgr.balanceRoles(); | ||
288 | + checkDeviceMasters(deviceIds, expectedMasters); | ||
289 | + | ||
290 | + // make N4 active | ||
291 | + testClusterService.put(CNODE4, ControllerNode.State.ACTIVE); | ||
292 | + expectedMasters.add(NID4); | ||
293 | + mgr.balanceRoles(); | ||
294 | + checkDeviceMasters(deviceIds, expectedMasters); | ||
295 | + | ||
296 | + // make N1 active | ||
297 | + testClusterService.put(CNODE1, ControllerNode.State.ACTIVE); | ||
298 | + expectedMasters.add(NID1); | ||
299 | + mgr.balanceRoles(); | ||
300 | + checkDeviceMasters(deviceIds, expectedMasters); | ||
301 | + } | ||
302 | + | ||
303 | + private void checkDeviceMasters(Set<DeviceId> deviceIds, Set<NodeId> expectedMasters) { | ||
304 | + checkDeviceMasters(deviceIds, expectedMasters, null); | ||
305 | + } | ||
306 | + | ||
307 | + private void checkDeviceMasters(Set<DeviceId> deviceIds, Set<NodeId> expectedMasters, | ||
308 | + Consumer<DeviceId> checkRole) { | ||
309 | + // each device's master must be contained in the list of expectedMasters | ||
310 | + deviceIds.stream().forEach(deviceId -> { | ||
311 | + assertTrue("wrong master:", expectedMasters.contains(mgr.getMasterFor(deviceId))); | ||
312 | + if (checkRole != null) { | ||
313 | + checkRole.accept(deviceId); | ||
314 | + } | ||
315 | + }); | ||
316 | + // each node in expectedMasters must have approximately the same number of devices | ||
317 | + if (expectedMasters.size() > 1) { | ||
318 | + int minValue = Integer.MAX_VALUE; | ||
319 | + int maxDevices = -1; | ||
320 | + for (NodeId nodeId: expectedMasters) { | ||
321 | + int numDevicesManagedByNode = mgr.getDevicesOf(nodeId).size(); | ||
322 | + if (numDevicesManagedByNode < minValue) { | ||
323 | + minValue = numDevicesManagedByNode; | ||
324 | + } | ||
325 | + if (numDevicesManagedByNode > maxDevices) { | ||
326 | + maxDevices = numDevicesManagedByNode; | ||
327 | + } | ||
328 | + assertTrue("not balanced:", maxDevices - minValue <= 1); | ||
329 | + } | ||
330 | + } | ||
331 | + } | ||
332 | + | ||
333 | + private final class TestClusterService extends StaticClusterService { | ||
158 | 334 | ||
159 | ControllerNode local = new DefaultControllerNode(NID_LOCAL, LOCALHOST); | 335 | ControllerNode local = new DefaultControllerNode(NID_LOCAL, LOCALHOST); |
160 | 336 | ||
... | @@ -163,11 +339,10 @@ public class MastershipManagerTest { | ... | @@ -163,11 +339,10 @@ public class MastershipManagerTest { |
163 | return local; | 339 | return local; |
164 | } | 340 | } |
165 | 341 | ||
166 | - @Override | 342 | + public void put(ControllerNode cn, ControllerNode.State state) { |
167 | - public Set<ControllerNode> getNodes() { | 343 | + nodes.put(cn.id(), cn); |
168 | - return Sets.newHashSet(); | 344 | + nodeStates.put(cn.id(), state); |
169 | } | 345 | } |
170 | - | ||
171 | } | 346 | } |
172 | 347 | ||
173 | private final class TestSimpleMastershipStore extends SimpleMastershipStore | 348 | private final class TestSimpleMastershipStore extends SimpleMastershipStore |
... | @@ -177,4 +352,10 @@ public class MastershipManagerTest { | ... | @@ -177,4 +352,10 @@ public class MastershipManagerTest { |
177 | super.clusterService = clusterService; | 352 | super.clusterService = clusterService; |
178 | } | 353 | } |
179 | } | 354 | } |
355 | + | ||
356 | + private class TestRegionManager extends RegionManager { | ||
357 | + TestRegionManager() { | ||
358 | + eventDispatcher = new TestEventDispatcher(); | ||
359 | + } | ||
360 | + } | ||
180 | } | 361 | } | ... | ... |
-
Please register or login to post a comment