Added ability to force mastership re-balancing between instances from the GUI.
Change-Id: I98e56deb3e2b00df630ed85b596c8e35b3d6efab
Showing
8 changed files
with
111 additions
and
95 deletions
... | @@ -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(); | ... | ... |
-
Please register or login to post a comment