blob: 3bd3cefa55fe2d1c921394c477ac8d238bad1e15 [file] [log] [blame]
Aaron Kruglikov29327982015-10-06 17:15:16 -07001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2015-present Open Networking Foundation
Aaron Kruglikov29327982015-10-06 17:15:16 -07003 *
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
17package org.onosproject.mlb;
18
Aaron Kruglikov29327982015-10-06 17:15:16 -070019import org.apache.felix.scr.annotations.Activate;
20import org.apache.felix.scr.annotations.Component;
21import org.apache.felix.scr.annotations.Deactivate;
sangyun-han55e17982016-08-03 15:45:38 +090022import org.apache.felix.scr.annotations.Modified;
23import org.apache.felix.scr.annotations.Property;
Aaron Kruglikov29327982015-10-06 17:15:16 -070024import org.apache.felix.scr.annotations.Reference;
25import org.apache.felix.scr.annotations.ReferenceCardinality;
sangyun-han55e17982016-08-03 15:45:38 +090026import org.onlab.util.Tools;
27import org.onosproject.cfg.ComponentConfigService;
Aaron Kruglikov29327982015-10-06 17:15:16 -070028import org.onosproject.cluster.ClusterService;
29import org.onosproject.cluster.LeadershipEvent;
30import org.onosproject.cluster.LeadershipEventListener;
31import org.onosproject.cluster.LeadershipService;
32import org.onosproject.cluster.NodeId;
33import org.onosproject.mastership.MastershipAdminService;
34import org.onosproject.mastership.MastershipEvent;
35import org.onosproject.mastership.MastershipListener;
36import org.onosproject.mastership.MastershipService;
sangyun-han55e17982016-08-03 15:45:38 +090037import org.osgi.service.component.ComponentContext;
Victor Silva6b1c6d22016-09-20 17:00:59 -030038import org.onosproject.net.region.RegionEvent;
39import org.onosproject.net.region.RegionListener;
40import org.onosproject.net.region.RegionService;
Aaron Kruglikov29327982015-10-06 17:15:16 -070041import org.slf4j.Logger;
42
sangyun-han55e17982016-08-03 15:45:38 +090043import java.util.Dictionary;
Victor Silva6b1c6d22016-09-20 17:00:59 -030044import java.util.concurrent.Executors;
Aaron Kruglikov29327982015-10-06 17:15:16 -070045import java.util.concurrent.Future;
Victor Silva6b1c6d22016-09-20 17:00:59 -030046import java.util.concurrent.ScheduledExecutorService;
Aaron Kruglikov29327982015-10-06 17:15:16 -070047import java.util.concurrent.TimeUnit;
48import java.util.concurrent.atomic.AtomicBoolean;
49import java.util.concurrent.atomic.AtomicReference;
50
Yuta HIGUCHI1624df12016-07-21 16:54:33 -070051import static org.onlab.util.Tools.groupedThreads;
Aaron Kruglikov29327982015-10-06 17:15:16 -070052import static org.slf4j.LoggerFactory.getLogger;
53
54/**
55 * An app to perform automatic load balancing in response to events. Load balancing events are triggered by any
56 * change in mastership and are limited to a frequency of one every 30 seconds, all load balancing is run on an outside
57 * thread executor that must only have one thread due to issues that can occur is multiple balancing events occur in
58 * parallel.
59 */
60@Component(immediate = true)
61public class MastershipLoadBalancer {
62
63 private final Logger log = getLogger(getClass());
64
Victor Silva6b1c6d22016-09-20 17:00:59 -030065 private static final int DEFAULT_SCHEDULE_PERIOD = 30;
sangyun-han55e17982016-08-03 15:45:38 +090066 @Property(name = "schedulePeriod", intValue = DEFAULT_SCHEDULE_PERIOD,
67 label = "Period to schedule balancing the mastership to be shared as evenly as by all online instances.")
68 private int schedulePeriod = DEFAULT_SCHEDULE_PERIOD;
69
Aaron Kruglikov29327982015-10-06 17:15:16 -070070 private static final String REBALANCE_MASTERSHIP = "rebalance/mastership";
71
72 private NodeId localId;
73
74 private AtomicBoolean isLeader = new AtomicBoolean(false);
75
76 private AtomicReference<Future> nextTask = new AtomicReference<>();
77
78 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
79 protected MastershipService mastershipService;
80
81 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
82 protected MastershipAdminService mastershipAdminService;
83
84 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
85 protected LeadershipService leadershipService;
86
87 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Victor Silva6b1c6d22016-09-20 17:00:59 -030088 protected RegionService regionService;
89
90 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Aaron Kruglikov29327982015-10-06 17:15:16 -070091 protected ClusterService clusterService;
92
sangyun-han55e17982016-08-03 15:45:38 +090093 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
94 protected ComponentConfigService cfgService;
95
Aaron Kruglikov29327982015-10-06 17:15:16 -070096 private InnerLeadershipListener leadershipListener = new InnerLeadershipListener();
97
Victor Silva6b1c6d22016-09-20 17:00:59 -030098 /* This listener is used to trigger balancing for any mastership event
99 * which will include switches changing state between active and inactive
100 * states as well as the same variety of event occurring with ONOS nodes.
Aaron Kruglikov29327982015-10-06 17:15:16 -0700101 */
102 private InnerMastershipListener mastershipListener = new InnerMastershipListener();
103
Victor Silva6b1c6d22016-09-20 17:00:59 -0300104 /* Used to trigger balancing on region events where there was either a
105 * change on the master sets of a given region or a change on the devices
106 * that belong to a region.
107 */
108 private InnerRegionListener regionEventListener = new InnerRegionListener();
109
110 /* Ensures that all executions do not interfere with one another (single
111 * thread) and that they are apart from each other by at least what is
112 * defined as the schedulePeriod.
113 */
114 private ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(
115 groupedThreads("MastershipLoadBalancer", "%d", log));
Aaron Kruglikov29327982015-10-06 17:15:16 -0700116
117 @Activate
sangyun-han55e17982016-08-03 15:45:38 +0900118 public void activate(ComponentContext context) {
119 cfgService.registerProperties(getClass());
120 modified(context);
Aaron Kruglikov29327982015-10-06 17:15:16 -0700121 mastershipService.addListener(mastershipListener);
122 localId = clusterService.getLocalNode().id();
123 leadershipService.addListener(leadershipListener);
124 leadershipService.runForLeadership(REBALANCE_MASTERSHIP);
Victor Silva6b1c6d22016-09-20 17:00:59 -0300125 regionService.addListener(regionEventListener);
Aaron Kruglikov29327982015-10-06 17:15:16 -0700126 log.info("Started");
127 }
128
129 @Deactivate
130 public void deactivate() {
sangyun-han55e17982016-08-03 15:45:38 +0900131 cfgService.unregisterProperties(getClass(), false);
Aaron Kruglikov29327982015-10-06 17:15:16 -0700132 mastershipService.removeListener(mastershipListener);
133 leadershipService.withdraw(REBALANCE_MASTERSHIP);
134 leadershipService.removeListener(leadershipListener);
Victor Silva6b1c6d22016-09-20 17:00:59 -0300135 regionService.removeListener(regionEventListener);
Aaron Kruglikov29327982015-10-06 17:15:16 -0700136 cancelBalance();
137 executorService.shutdown();
138 log.info("Stopped");
139 }
140
sangyun-han55e17982016-08-03 15:45:38 +0900141 @Modified
142 public void modified(ComponentContext context) {
143 readComponentConfiguration(context);
144 cancelBalance();
145 scheduleBalance();
146 log.info("modified");
147 }
148
Madan Jampani620f70d2016-01-30 22:22:47 -0800149 private synchronized void processLeaderChange(NodeId newLeader) {
Aaron Kruglikov29327982015-10-06 17:15:16 -0700150 boolean currLeader = newLeader.equals(localId);
151 if (isLeader.getAndSet(currLeader) != currLeader) {
152 if (currLeader) {
153 scheduleBalance();
154 } else {
155 cancelBalance();
156 }
157 }
158 }
159
Victor Silva6b1c6d22016-09-20 17:00:59 -0300160 // Sets flag at execution to indicate there is currently a scheduled
161 // rebalancing. As soon as it starts running, the flag is set back to
162 // null and another rebalancing can be queued.
Aaron Kruglikov29327982015-10-06 17:15:16 -0700163 private void scheduleBalance() {
164 if (isLeader.get() && nextTask.get() == null) {
165
Victor Silva6b1c6d22016-09-20 17:00:59 -0300166 Future task = executorService.schedule(new BalanceTask(),
167 schedulePeriod, TimeUnit.SECONDS);
168
Aaron Kruglikov29327982015-10-06 17:15:16 -0700169 if (!nextTask.compareAndSet(null, task)) {
170 task.cancel(false);
171 }
172 }
173 }
174
Victor Silva6b1c6d22016-09-20 17:00:59 -0300175 private class BalanceTask implements Runnable {
176
177 @Override
178 public void run() {
179 // nextTask is now running, free the spot so that it is possible
180 // to queue up another upcoming task.
181 nextTask.set(null);
182
183 mastershipAdminService.balanceRoles();
184 log.info("Completed balance roles");
185 }
186 }
187
Aaron Kruglikov29327982015-10-06 17:15:16 -0700188 private void cancelBalance() {
189 Future task = nextTask.getAndSet(null);
190 if (task != null) {
191 task.cancel(false);
192 }
193 }
194
sangyun-han55e17982016-08-03 15:45:38 +0900195 /**
196 * Extracts properties from the component configuration context.
197 *
198 * @param context the component context
199 */
200 private void readComponentConfiguration(ComponentContext context) {
201 Dictionary<?, ?> properties = context.getProperties();
202
203 Integer newSchedulePeriod = Tools.getIntegerProperty(properties,
204 "schedulePeriod");
205 if (newSchedulePeriod == null) {
206 schedulePeriod = DEFAULT_SCHEDULE_PERIOD;
207 log.info("Schedule period is not configured, default value is {}",
208 DEFAULT_SCHEDULE_PERIOD);
209 } else {
210 schedulePeriod = newSchedulePeriod;
211 log.info("Configured. Schedule period is configured to {}", schedulePeriod);
212 }
213 }
214
Aaron Kruglikov29327982015-10-06 17:15:16 -0700215 private class InnerMastershipListener implements MastershipListener {
216
217 @Override
218 public void event(MastershipEvent event) {
Aaron Kruglikov29327982015-10-06 17:15:16 -0700219 scheduleBalance();
220 }
221 }
222
223 private class InnerLeadershipListener implements LeadershipEventListener {
224 @Override
225 public boolean isRelevant(LeadershipEvent event) {
226 return REBALANCE_MASTERSHIP.equals(event.subject().topic());
227 }
228
229 @Override
230 public void event(LeadershipEvent event) {
Madan Jampani620f70d2016-01-30 22:22:47 -0800231 processLeaderChange(event.subject().leaderNodeId());
Aaron Kruglikov29327982015-10-06 17:15:16 -0700232 }
233 }
Victor Silva6b1c6d22016-09-20 17:00:59 -0300234
235 private class InnerRegionListener implements RegionListener {
236 @Override
237 public void event(RegionEvent event) {
238 switch (event.type()) {
239 case REGION_MEMBERSHIP_CHANGED:
240 case REGION_UPDATED:
241 scheduleBalance();
242 break;
243 default:
244 break;
245 }
246 }
247 }
Madan Jampani620f70d2016-01-30 22:22:47 -0800248}