blob: f9ed0da3f9b07e0be20a37dcf14a315015b213e0 [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
sangyun-han55e17982016-08-03 15:45:38 +090019import org.onlab.util.Tools;
20import org.onosproject.cfg.ComponentConfigService;
Aaron Kruglikov29327982015-10-06 17:15:16 -070021import org.onosproject.cluster.ClusterService;
22import org.onosproject.cluster.LeadershipEvent;
23import org.onosproject.cluster.LeadershipEventListener;
24import org.onosproject.cluster.LeadershipService;
25import org.onosproject.cluster.NodeId;
26import org.onosproject.mastership.MastershipAdminService;
27import org.onosproject.mastership.MastershipEvent;
28import org.onosproject.mastership.MastershipListener;
29import org.onosproject.mastership.MastershipService;
Victor Silva6b1c6d22016-09-20 17:00:59 -030030import org.onosproject.net.region.RegionEvent;
31import org.onosproject.net.region.RegionListener;
32import org.onosproject.net.region.RegionService;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070033import org.osgi.service.component.ComponentContext;
34import org.osgi.service.component.annotations.Activate;
35import org.osgi.service.component.annotations.Component;
36import org.osgi.service.component.annotations.Deactivate;
37import org.osgi.service.component.annotations.Modified;
38import org.osgi.service.component.annotations.Reference;
39import org.osgi.service.component.annotations.ReferenceCardinality;
Aaron Kruglikov29327982015-10-06 17:15:16 -070040import org.slf4j.Logger;
41
sangyun-han55e17982016-08-03 15:45:38 +090042import java.util.Dictionary;
Victor Silva6b1c6d22016-09-20 17:00:59 -030043import java.util.concurrent.Executors;
Aaron Kruglikov29327982015-10-06 17:15:16 -070044import java.util.concurrent.Future;
Victor Silva6b1c6d22016-09-20 17:00:59 -030045import java.util.concurrent.ScheduledExecutorService;
Aaron Kruglikov29327982015-10-06 17:15:16 -070046import java.util.concurrent.TimeUnit;
47import java.util.concurrent.atomic.AtomicBoolean;
48import java.util.concurrent.atomic.AtomicReference;
49
Yuta HIGUCHI1624df12016-07-21 16:54:33 -070050import static org.onlab.util.Tools.groupedThreads;
Ray Milkey4694e062018-10-31 13:17:18 -070051import static org.onosproject.mlb.OsgiPropertyConstants.SCHEDULE_PERIOD;
52import static org.onosproject.mlb.OsgiPropertyConstants.SCHEDULE_PERIOD_DEFAULT;
Aaron Kruglikov29327982015-10-06 17:15:16 -070053import static org.slf4j.LoggerFactory.getLogger;
54
55/**
56 * An app to perform automatic load balancing in response to events. Load balancing events are triggered by any
57 * change in mastership and are limited to a frequency of one every 30 seconds, all load balancing is run on an outside
58 * thread executor that must only have one thread due to issues that can occur is multiple balancing events occur in
59 * parallel.
60 */
Ray Milkey4694e062018-10-31 13:17:18 -070061@Component(
62 immediate = true,
63 property = {
64 SCHEDULE_PERIOD + ":Integer=" + SCHEDULE_PERIOD_DEFAULT
65 }
66)
Aaron Kruglikov29327982015-10-06 17:15:16 -070067public class MastershipLoadBalancer {
68
69 private final Logger log = getLogger(getClass());
70
Ray Milkey4694e062018-10-31 13:17:18 -070071 /** Period to schedule balancing the mastership to be shared as evenly as by all online instances. */
72 private int schedulePeriod = SCHEDULE_PERIOD_DEFAULT;
sangyun-han55e17982016-08-03 15:45:38 +090073
Aaron Kruglikov29327982015-10-06 17:15:16 -070074 private static final String REBALANCE_MASTERSHIP = "rebalance/mastership";
75
76 private NodeId localId;
77
78 private AtomicBoolean isLeader = new AtomicBoolean(false);
79
80 private AtomicReference<Future> nextTask = new AtomicReference<>();
81
Ray Milkeyd84f89b2018-08-17 14:54:17 -070082 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Aaron Kruglikov29327982015-10-06 17:15:16 -070083 protected MastershipService mastershipService;
84
Ray Milkeyd84f89b2018-08-17 14:54:17 -070085 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Aaron Kruglikov29327982015-10-06 17:15:16 -070086 protected MastershipAdminService mastershipAdminService;
87
Ray Milkeyd84f89b2018-08-17 14:54:17 -070088 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Aaron Kruglikov29327982015-10-06 17:15:16 -070089 protected LeadershipService leadershipService;
90
Ray Milkeyd84f89b2018-08-17 14:54:17 -070091 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Victor Silva6b1c6d22016-09-20 17:00:59 -030092 protected RegionService regionService;
93
Ray Milkeyd84f89b2018-08-17 14:54:17 -070094 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Aaron Kruglikov29327982015-10-06 17:15:16 -070095 protected ClusterService clusterService;
96
Ray Milkeyd84f89b2018-08-17 14:54:17 -070097 @Reference(cardinality = ReferenceCardinality.MANDATORY)
sangyun-han55e17982016-08-03 15:45:38 +090098 protected ComponentConfigService cfgService;
99
Aaron Kruglikov29327982015-10-06 17:15:16 -0700100 private InnerLeadershipListener leadershipListener = new InnerLeadershipListener();
101
Victor Silva6b1c6d22016-09-20 17:00:59 -0300102 /* This listener is used to trigger balancing for any mastership event
103 * which will include switches changing state between active and inactive
104 * states as well as the same variety of event occurring with ONOS nodes.
Aaron Kruglikov29327982015-10-06 17:15:16 -0700105 */
106 private InnerMastershipListener mastershipListener = new InnerMastershipListener();
107
Victor Silva6b1c6d22016-09-20 17:00:59 -0300108 /* Used to trigger balancing on region events where there was either a
109 * change on the master sets of a given region or a change on the devices
110 * that belong to a region.
111 */
112 private InnerRegionListener regionEventListener = new InnerRegionListener();
113
114 /* Ensures that all executions do not interfere with one another (single
115 * thread) and that they are apart from each other by at least what is
116 * defined as the schedulePeriod.
117 */
118 private ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(
119 groupedThreads("MastershipLoadBalancer", "%d", log));
Aaron Kruglikov29327982015-10-06 17:15:16 -0700120
121 @Activate
sangyun-han55e17982016-08-03 15:45:38 +0900122 public void activate(ComponentContext context) {
123 cfgService.registerProperties(getClass());
124 modified(context);
Aaron Kruglikov29327982015-10-06 17:15:16 -0700125 mastershipService.addListener(mastershipListener);
126 localId = clusterService.getLocalNode().id();
127 leadershipService.addListener(leadershipListener);
128 leadershipService.runForLeadership(REBALANCE_MASTERSHIP);
Victor Silva6b1c6d22016-09-20 17:00:59 -0300129 regionService.addListener(regionEventListener);
Aaron Kruglikov29327982015-10-06 17:15:16 -0700130 log.info("Started");
131 }
132
133 @Deactivate
134 public void deactivate() {
sangyun-han55e17982016-08-03 15:45:38 +0900135 cfgService.unregisterProperties(getClass(), false);
Aaron Kruglikov29327982015-10-06 17:15:16 -0700136 mastershipService.removeListener(mastershipListener);
137 leadershipService.withdraw(REBALANCE_MASTERSHIP);
138 leadershipService.removeListener(leadershipListener);
Victor Silva6b1c6d22016-09-20 17:00:59 -0300139 regionService.removeListener(regionEventListener);
Aaron Kruglikov29327982015-10-06 17:15:16 -0700140 cancelBalance();
141 executorService.shutdown();
142 log.info("Stopped");
143 }
144
sangyun-han55e17982016-08-03 15:45:38 +0900145 @Modified
146 public void modified(ComponentContext context) {
147 readComponentConfiguration(context);
148 cancelBalance();
149 scheduleBalance();
150 log.info("modified");
151 }
152
Madan Jampani620f70d2016-01-30 22:22:47 -0800153 private synchronized void processLeaderChange(NodeId newLeader) {
Aaron Kruglikov29327982015-10-06 17:15:16 -0700154 boolean currLeader = newLeader.equals(localId);
155 if (isLeader.getAndSet(currLeader) != currLeader) {
156 if (currLeader) {
157 scheduleBalance();
158 } else {
159 cancelBalance();
160 }
161 }
162 }
163
Victor Silva6b1c6d22016-09-20 17:00:59 -0300164 // Sets flag at execution to indicate there is currently a scheduled
165 // rebalancing. As soon as it starts running, the flag is set back to
166 // null and another rebalancing can be queued.
Aaron Kruglikov29327982015-10-06 17:15:16 -0700167 private void scheduleBalance() {
168 if (isLeader.get() && nextTask.get() == null) {
169
Victor Silva6b1c6d22016-09-20 17:00:59 -0300170 Future task = executorService.schedule(new BalanceTask(),
171 schedulePeriod, TimeUnit.SECONDS);
172
Aaron Kruglikov29327982015-10-06 17:15:16 -0700173 if (!nextTask.compareAndSet(null, task)) {
174 task.cancel(false);
175 }
176 }
177 }
178
Victor Silva6b1c6d22016-09-20 17:00:59 -0300179 private class BalanceTask implements Runnable {
180
181 @Override
182 public void run() {
183 // nextTask is now running, free the spot so that it is possible
184 // to queue up another upcoming task.
185 nextTask.set(null);
186
187 mastershipAdminService.balanceRoles();
188 log.info("Completed balance roles");
189 }
190 }
191
Aaron Kruglikov29327982015-10-06 17:15:16 -0700192 private void cancelBalance() {
193 Future task = nextTask.getAndSet(null);
194 if (task != null) {
195 task.cancel(false);
196 }
197 }
198
sangyun-han55e17982016-08-03 15:45:38 +0900199 /**
200 * Extracts properties from the component configuration context.
201 *
202 * @param context the component context
203 */
204 private void readComponentConfiguration(ComponentContext context) {
205 Dictionary<?, ?> properties = context.getProperties();
206
207 Integer newSchedulePeriod = Tools.getIntegerProperty(properties,
Ray Milkey4694e062018-10-31 13:17:18 -0700208 SCHEDULE_PERIOD);
sangyun-han55e17982016-08-03 15:45:38 +0900209 if (newSchedulePeriod == null) {
Ray Milkey4694e062018-10-31 13:17:18 -0700210 schedulePeriod = SCHEDULE_PERIOD_DEFAULT;
sangyun-han55e17982016-08-03 15:45:38 +0900211 log.info("Schedule period is not configured, default value is {}",
Ray Milkey4694e062018-10-31 13:17:18 -0700212 SCHEDULE_PERIOD_DEFAULT);
sangyun-han55e17982016-08-03 15:45:38 +0900213 } else {
214 schedulePeriod = newSchedulePeriod;
215 log.info("Configured. Schedule period is configured to {}", schedulePeriod);
216 }
217 }
218
Aaron Kruglikov29327982015-10-06 17:15:16 -0700219 private class InnerMastershipListener implements MastershipListener {
220
221 @Override
222 public void event(MastershipEvent event) {
Aaron Kruglikov29327982015-10-06 17:15:16 -0700223 scheduleBalance();
224 }
225 }
226
227 private class InnerLeadershipListener implements LeadershipEventListener {
228 @Override
229 public boolean isRelevant(LeadershipEvent event) {
230 return REBALANCE_MASTERSHIP.equals(event.subject().topic());
231 }
232
233 @Override
234 public void event(LeadershipEvent event) {
Madan Jampani620f70d2016-01-30 22:22:47 -0800235 processLeaderChange(event.subject().leaderNodeId());
Aaron Kruglikov29327982015-10-06 17:15:16 -0700236 }
237 }
Victor Silva6b1c6d22016-09-20 17:00:59 -0300238
239 private class InnerRegionListener implements RegionListener {
240 @Override
241 public void event(RegionEvent event) {
242 switch (event.type()) {
243 case REGION_MEMBERSHIP_CHANGED:
244 case REGION_UPDATED:
245 scheduleBalance();
246 break;
247 default:
248 break;
249 }
250 }
251 }
Madan Jampani620f70d2016-01-30 22:22:47 -0800252}