Claudine Chiu
Committed by Gerrit Code Review

[Goldeneye] ONOS-4017: Mastership service considers Region information when determining mastership.

Change-Id: I6c79239f2e071d865bf04e4d9d790ca9b2d04694
...@@ -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 }
......