Thomas Vachuska

Added ability to force mastership re-balancing between instances from the GUI.

Change-Id: I98e56deb3e2b00df630ed85b596c8e35b3d6efab
...@@ -15,23 +15,8 @@ ...@@ -15,23 +15,8 @@
15 */ 15 */
16 package org.onlab.onos.cli; 16 package org.onlab.onos.cli;
17 17
18 -import com.google.common.collect.HashMultimap;
19 -import com.google.common.collect.Multimap;
20 import org.apache.karaf.shell.commands.Command; 18 import org.apache.karaf.shell.commands.Command;
21 -import org.onlab.onos.cluster.ClusterService;
22 -import org.onlab.onos.cluster.ControllerNode;
23 import org.onlab.onos.mastership.MastershipAdminService; 19 import org.onlab.onos.mastership.MastershipAdminService;
24 -import org.onlab.onos.mastership.MastershipService;
25 -import org.onlab.onos.net.DeviceId;
26 -import org.onlab.onos.net.device.DeviceService;
27 -
28 -import java.util.Collection;
29 -import java.util.Iterator;
30 -import java.util.List;
31 -import java.util.Set;
32 -
33 -import static com.google.common.collect.Lists.newArrayList;
34 -import static org.onlab.onos.net.MastershipRole.MASTER;
35 20
36 /** 21 /**
37 * Forces device mastership rebalancing. 22 * Forces device mastership rebalancing.
...@@ -42,72 +27,7 @@ public class BalanceMastersCommand extends AbstractShellCommand { ...@@ -42,72 +27,7 @@ public class BalanceMastersCommand extends AbstractShellCommand {
42 27
43 @Override 28 @Override
44 protected void execute() { 29 protected void execute() {
45 - ClusterService service = get(ClusterService.class); 30 + get(MastershipAdminService.class).balanceRoles();
46 - MastershipService mastershipService = get(MastershipService.class);
47 - MastershipAdminService adminService = get(MastershipAdminService.class);
48 -
49 - List<ControllerNode> nodes = newArrayList(service.getNodes());
50 -
51 - Multimap<ControllerNode, DeviceId> controllerDevices = HashMultimap.create();
52 -
53 - // Create buckets reflecting current ownership.
54 - for (ControllerNode node : nodes) {
55 - Set<DeviceId> devicesOf = mastershipService.getDevicesOf(node.id());
56 - controllerDevices.putAll(node, devicesOf);
57 - print("Node %s has %d devices.", node.id(), devicesOf.size());
58 - }
59 -
60 - int rounds = nodes.size();
61 - for (int i = 0; i < rounds; i++) {
62 - // Iterate over the buckets and find the smallest and the largest.
63 - ControllerNode smallest = findBucket(true, nodes, controllerDevices);
64 - ControllerNode largest = findBucket(false, nodes, controllerDevices);
65 - balanceBuckets(smallest, largest, controllerDevices, adminService);
66 - }
67 - }
68 -
69 - private ControllerNode findBucket(boolean min, Collection<ControllerNode> nodes,
70 - Multimap<ControllerNode, DeviceId> controllerDevices) {
71 - int xSize = min ? Integer.MAX_VALUE : -1;
72 - ControllerNode xNode = null;
73 - for (ControllerNode node : nodes) {
74 - int size = controllerDevices.get(node).size();
75 - if ((min && size < xSize) || (!min && size > xSize)) {
76 - xSize = size;
77 - xNode = node;
78 - }
79 - }
80 - return xNode;
81 - }
82 -
83 - // FIXME: enhance to better handle cases where smallest cannot take any of the devices from largest
84 -
85 - private void balanceBuckets(ControllerNode smallest, ControllerNode largest,
86 - Multimap<ControllerNode, DeviceId> controllerDevices,
87 - MastershipAdminService adminService) {
88 - Collection<DeviceId> minBucket = controllerDevices.get(smallest);
89 - Collection<DeviceId> maxBucket = controllerDevices.get(largest);
90 - int bucketCount = controllerDevices.keySet().size();
91 - int deviceCount = get(DeviceService.class).getDeviceCount();
92 -
93 - int delta = (maxBucket.size() - minBucket.size()) / 2;
94 - delta = Math.min(deviceCount / bucketCount, delta);
95 -
96 - if (delta > 0) {
97 - print("Attempting to move %d nodes from %s to %s...",
98 - delta, largest.id(), smallest.id());
99 -
100 - int i = 0;
101 - Iterator<DeviceId> it = maxBucket.iterator();
102 - while (it.hasNext() && i < delta) {
103 - DeviceId deviceId = it.next();
104 - print("Setting %s as the master for %s", smallest.id(), deviceId);
105 - adminService.setRole(smallest.id(), deviceId, MASTER);
106 - controllerDevices.put(smallest, deviceId);
107 - it.remove();
108 - i++;
109 - }
110 - }
111 } 31 }
112 32
113 } 33 }
......
...@@ -33,4 +33,10 @@ public interface MastershipAdminService { ...@@ -33,4 +33,10 @@ public interface MastershipAdminService {
33 */ 33 */
34 void setRole(NodeId instance, DeviceId deviceId, MastershipRole role); 34 void setRole(NodeId instance, DeviceId deviceId, MastershipRole role);
35 35
36 + /**
37 + * Balances the mastership to be shared as evenly as possibly by all
38 + * online instances.
39 + */
40 + void balanceRoles();
41 +
36 } 42 }
......
...@@ -15,13 +15,10 @@ ...@@ -15,13 +15,10 @@
15 */ 15 */
16 package org.onlab.onos.cluster.impl; 16 package org.onlab.onos.cluster.impl;
17 17
18 -import static com.google.common.base.Preconditions.checkNotNull; 18 +import com.codahale.metrics.Timer;
19 -import static org.slf4j.LoggerFactory.getLogger; 19 +import com.codahale.metrics.Timer.Context;
20 -import static org.onlab.metrics.MetricsUtil.*; 20 +import com.google.common.collect.HashMultimap;
21 - 21 +import com.google.common.collect.Multimap;
22 -import java.util.Set;
23 -import java.util.concurrent.atomic.AtomicInteger;
24 -
25 import org.apache.felix.scr.annotations.Activate; 22 import org.apache.felix.scr.annotations.Activate;
26 import org.apache.felix.scr.annotations.Component; 23 import org.apache.felix.scr.annotations.Component;
27 import org.apache.felix.scr.annotations.Deactivate; 24 import org.apache.felix.scr.annotations.Deactivate;
...@@ -50,8 +47,18 @@ import org.onlab.onos.net.DeviceId; ...@@ -50,8 +47,18 @@ import org.onlab.onos.net.DeviceId;
50 import org.onlab.onos.net.MastershipRole; 47 import org.onlab.onos.net.MastershipRole;
51 import org.slf4j.Logger; 48 import org.slf4j.Logger;
52 49
53 -import com.codahale.metrics.Timer; 50 +import java.util.Collection;
54 -import com.codahale.metrics.Timer.Context; 51 +import java.util.Iterator;
52 +import java.util.List;
53 +import java.util.Set;
54 +import java.util.concurrent.atomic.AtomicInteger;
55 +
56 +import static com.google.common.base.Preconditions.checkNotNull;
57 +import static com.google.common.collect.Lists.newArrayList;
58 +import static org.onlab.metrics.MetricsUtil.startTimer;
59 +import static org.onlab.metrics.MetricsUtil.stopTimer;
60 +import static org.onlab.onos.net.MastershipRole.MASTER;
61 +import static org.slf4j.LoggerFactory.getLogger;
55 62
56 @Component(immediate = true) 63 @Component(immediate = true)
57 @Service 64 @Service
...@@ -198,6 +205,71 @@ public class MastershipManager ...@@ -198,6 +205,71 @@ public class MastershipManager
198 return metricsService; 205 return metricsService;
199 } 206 }
200 207
208 + @Override
209 + public void balanceRoles() {
210 + List<ControllerNode> nodes = newArrayList(clusterService.getNodes());
211 + Multimap<ControllerNode, DeviceId> controllerDevices = HashMultimap.create();
212 + int deviceCount = 0;
213 +
214 + // Create buckets reflecting current ownership.
215 + for (ControllerNode node : nodes) {
216 + Set<DeviceId> devicesOf = getDevicesOf(node.id());
217 + deviceCount += devicesOf.size();
218 + controllerDevices.putAll(node, devicesOf);
219 + log.info("Node {} has {} devices.", node.id(), devicesOf.size());
220 + }
221 +
222 + int rounds = nodes.size();
223 + for (int i = 0; i < rounds; i++) {
224 + // Iterate over the buckets and find the smallest and the largest.
225 + ControllerNode smallest = findBucket(true, nodes, controllerDevices);
226 + ControllerNode largest = findBucket(false, nodes, controllerDevices);
227 + balanceBuckets(smallest, largest, controllerDevices, deviceCount);
228 + }
229 + }
230 +
231 + private ControllerNode findBucket(boolean min, Collection<ControllerNode> nodes,
232 + Multimap<ControllerNode, DeviceId> controllerDevices) {
233 + int xSize = min ? Integer.MAX_VALUE : -1;
234 + ControllerNode xNode = null;
235 + for (ControllerNode node : nodes) {
236 + int size = controllerDevices.get(node).size();
237 + if ((min && size < xSize) || (!min && size > xSize)) {
238 + xSize = size;
239 + xNode = node;
240 + }
241 + }
242 + return xNode;
243 + }
244 +
245 + private void balanceBuckets(ControllerNode smallest, ControllerNode largest,
246 + Multimap<ControllerNode, DeviceId> controllerDevices,
247 + int deviceCount) {
248 + Collection<DeviceId> minBucket = controllerDevices.get(smallest);
249 + Collection<DeviceId> maxBucket = controllerDevices.get(largest);
250 + int bucketCount = controllerDevices.keySet().size();
251 +
252 + int delta = (maxBucket.size() - minBucket.size()) / 2;
253 + delta = Math.min(deviceCount / bucketCount, delta);
254 +
255 + if (delta > 0) {
256 + log.info("Attempting to move {} nodes from {} to {}...", delta,
257 + largest.id(), smallest.id());
258 +
259 + int i = 0;
260 + Iterator<DeviceId> it = maxBucket.iterator();
261 + while (it.hasNext() && i < delta) {
262 + DeviceId deviceId = it.next();
263 + log.info("Setting {} as the master for {}", smallest.id(), deviceId);
264 + setRole(smallest.id(), deviceId, MASTER);
265 + controllerDevices.put(smallest, deviceId);
266 + it.remove();
267 + i++;
268 + }
269 + }
270 + }
271 +
272 +
201 // Posts the specified event to the local event dispatcher. 273 // Posts the specified event to the local event dispatcher.
202 private void post(MastershipEvent event) { 274 private void post(MastershipEvent event) {
203 if (event != null && eventDispatcher != null) { 275 if (event != null && eventDispatcher != null) {
......
...@@ -27,6 +27,7 @@ import org.onlab.onos.core.CoreService; ...@@ -27,6 +27,7 @@ import org.onlab.onos.core.CoreService;
27 import org.onlab.onos.event.AbstractEventAccumulator; 27 import org.onlab.onos.event.AbstractEventAccumulator;
28 import org.onlab.onos.event.Event; 28 import org.onlab.onos.event.Event;
29 import org.onlab.onos.event.EventAccumulator; 29 import org.onlab.onos.event.EventAccumulator;
30 +import org.onlab.onos.mastership.MastershipAdminService;
30 import org.onlab.onos.mastership.MastershipEvent; 31 import org.onlab.onos.mastership.MastershipEvent;
31 import org.onlab.onos.mastership.MastershipListener; 32 import org.onlab.onos.mastership.MastershipListener;
32 import org.onlab.onos.net.ConnectPoint; 33 import org.onlab.onos.net.ConnectPoint;
...@@ -233,6 +234,9 @@ public class TopologyViewWebSocket ...@@ -233,6 +234,9 @@ public class TopologyViewWebSocket
233 requestSummary(event); 234 requestSummary(event);
234 } else if (type.equals("cancelSummary")) { 235 } else if (type.equals("cancelSummary")) {
235 cancelSummary(event); 236 cancelSummary(event);
237 +
238 + } else if (type.equals("equalizeMasters")) {
239 + equalizeMasters(event);
236 } 240 }
237 } 241 }
238 242
...@@ -449,6 +453,12 @@ public class TopologyViewWebSocket ...@@ -449,6 +453,12 @@ public class TopologyViewWebSocket
449 } 453 }
450 454
451 455
456 + // Forces mastership role rebalancing.
457 + private void equalizeMasters(ObjectNode event) {
458 + directory.get(MastershipAdminService.class).balanceRoles();
459 + }
460 +
461 +
452 // Adds all internal listeners. 462 // Adds all internal listeners.
453 private void addListeners() { 463 private void addListeners() {
454 clusterService.addListener(clusterListener); 464 clusterService.addListener(clusterListener);
......
...@@ -140,12 +140,12 @@ ...@@ -140,12 +140,12 @@
140 // TODO: tune colors for light and dark themes 140 // TODO: tune colors for light and dark themes
141 // Note: These colors look good on the white background. Still, need to tune for dark. 141 // Note: These colors look good on the white background. Still, need to tune for dark.
142 142
143 - // blue brown purple sea green brick red dark teal lime 143 + // blue brown brick red sea green purple dark teal lime
144 - var lightNorm = ['#3E5780', '#78533B', '#8A2979', '#018D61', '#CB4D28', '#006D73', '#56AF00'], 144 + var lightNorm = ['#3E5780', '#78533B', '#CB4D28', '#018D61', '#8A2979', '#006D73', '#56AF00'],
145 - lightMute = ['#A8B8CC', '#CCB3A8', '#D19FCE', '#96D6BF', '#FFC2BD', '#8FCCCA', '#CAEAA4'], 145 + lightMute = ['#A8B8CC', '#CCB3A8', '#FFC2BD', '#96D6BF', '#D19FCE', '#8FCCCA', '#CAEAA4'],
146 146
147 - darkNorm = ['#3E5780', '#78533B', '#8A2979', '#018D61', '#CB4D28', '#006D73', '#56AF00'], 147 + darkNorm = ['#3E5780', '#78533B', '#CB4D28', '#018D61', '#8A2979', '#006D73', '#56AF00'],
148 - darkMute = ['#A8B8CC', '#CCB3A8', '#D19FCE', '#96D6BF', '#FFC2BD', '#8FCCCA', '#CAEAA4']; 148 + darkMute = ['#A8B8CC', '#CCB3A8', '#FFC2BD', '#96D6BF', '#D19FCE', '#8FCCCA', '#CAEAA4'];
149 149
150 function cat7() { 150 function cat7() {
151 var colors = { 151 var colors = {
......
...@@ -97,6 +97,7 @@ ...@@ -97,6 +97,7 @@
97 case 187: return 'equals'; 97 case 187: return 'equals';
98 case 189: return 'dash'; 98 case 189: return 'dash';
99 case 191: return 'slash'; 99 case 191: return 'slash';
100 + case 192: return 'backQuote';
100 default: 101 default:
101 if ((code >= 48 && code <= 57) || 102 if ((code >= 48 && code <= 57) ||
102 (code >= 65 && code <= 90)) { 103 (code >= 65 && code <= 90)) {
......
...@@ -55,6 +55,7 @@ ...@@ -55,6 +55,7 @@
55 equals: '=', 55 equals: '=',
56 dash: '-', 56 dash: '-',
57 slash: '/', 57 slash: '/',
58 + backQuote: '`',
58 leftArrow: 'L-arrow', 59 leftArrow: 'L-arrow',
59 upArrow: 'U-arrow', 60 upArrow: 'U-arrow',
60 rightArrow: 'R-arrow', 61 rightArrow: 'R-arrow',
......
...@@ -140,6 +140,7 @@ ...@@ -140,6 +140,7 @@
140 equals: injectStartupEvents, 140 equals: injectStartupEvents,
141 dash: injectTestEvent, 141 dash: injectTestEvent,
142 142
143 + E: [equalizeMasters, 'Equalize mastership roles'],
143 O: [toggleSummary, 'Toggle ONOS summary pane'], 144 O: [toggleSummary, 'Toggle ONOS summary pane'],
144 I: [toggleInstances, 'Toggle ONOS instances pane'], 145 I: [toggleInstances, 'Toggle ONOS instances pane'],
145 D: [toggleDetails, 'Disable / enable details pane'], 146 D: [toggleDetails, 'Disable / enable details pane'],
...@@ -926,6 +927,11 @@ ...@@ -926,6 +927,11 @@
926 updateDeviceColors(); 927 updateDeviceColors();
927 } 928 }
928 929
930 + function equalizeMasters() {
931 + flash('Equalizing master roles');
932 + sendMessage('equalizeMasters');
933 + }
934 +
929 function toggleSummary() { 935 function toggleSummary() {
930 if (!summaryPane.isVisible()) { 936 if (!summaryPane.isVisible()) {
931 requestSummary(); 937 requestSummary();
......