blob: ca6cd595cf47e1cae72e1583b10480e944f0fc9b [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;
Aaron Kruglikov29327982015-10-06 17:15:16 -070051import static org.slf4j.LoggerFactory.getLogger;
52
53/**
54 * An app to perform automatic load balancing in response to events. Load balancing events are triggered by any
55 * change in mastership and are limited to a frequency of one every 30 seconds, all load balancing is run on an outside
56 * thread executor that must only have one thread due to issues that can occur is multiple balancing events occur in
57 * parallel.
58 */
59@Component(immediate = true)
60public class MastershipLoadBalancer {
61
62 private final Logger log = getLogger(getClass());
63
Victor Silva6b1c6d22016-09-20 17:00:59 -030064 private static final int DEFAULT_SCHEDULE_PERIOD = 30;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070065 //@Property(name = "schedulePeriod", intValue = DEFAULT_SCHEDULE_PERIOD,
66 // label = "Period to schedule balancing the mastership to be shared as evenly as by all online instances.")
sangyun-han55e17982016-08-03 15:45:38 +090067 private int schedulePeriod = DEFAULT_SCHEDULE_PERIOD;
68
Aaron Kruglikov29327982015-10-06 17:15:16 -070069 private static final String REBALANCE_MASTERSHIP = "rebalance/mastership";
70
71 private NodeId localId;
72
73 private AtomicBoolean isLeader = new AtomicBoolean(false);
74
75 private AtomicReference<Future> nextTask = new AtomicReference<>();
76
Ray Milkeyd84f89b2018-08-17 14:54:17 -070077 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Aaron Kruglikov29327982015-10-06 17:15:16 -070078 protected MastershipService mastershipService;
79
Ray Milkeyd84f89b2018-08-17 14:54:17 -070080 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Aaron Kruglikov29327982015-10-06 17:15:16 -070081 protected MastershipAdminService mastershipAdminService;
82
Ray Milkeyd84f89b2018-08-17 14:54:17 -070083 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Aaron Kruglikov29327982015-10-06 17:15:16 -070084 protected LeadershipService leadershipService;
85
Ray Milkeyd84f89b2018-08-17 14:54:17 -070086 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Victor Silva6b1c6d22016-09-20 17:00:59 -030087 protected RegionService regionService;
88
Ray Milkeyd84f89b2018-08-17 14:54:17 -070089 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Aaron Kruglikov29327982015-10-06 17:15:16 -070090 protected ClusterService clusterService;
91
Ray Milkeyd84f89b2018-08-17 14:54:17 -070092 @Reference(cardinality = ReferenceCardinality.MANDATORY)
sangyun-han55e17982016-08-03 15:45:38 +090093 protected ComponentConfigService cfgService;
94
Aaron Kruglikov29327982015-10-06 17:15:16 -070095 private InnerLeadershipListener leadershipListener = new InnerLeadershipListener();
96
Victor Silva6b1c6d22016-09-20 17:00:59 -030097 /* This listener is used to trigger balancing for any mastership event
98 * which will include switches changing state between active and inactive
99 * states as well as the same variety of event occurring with ONOS nodes.
Aaron Kruglikov29327982015-10-06 17:15:16 -0700100 */
101 private InnerMastershipListener mastershipListener = new InnerMastershipListener();
102
Victor Silva6b1c6d22016-09-20 17:00:59 -0300103 /* Used to trigger balancing on region events where there was either a
104 * change on the master sets of a given region or a change on the devices
105 * that belong to a region.
106 */
107 private InnerRegionListener regionEventListener = new InnerRegionListener();
108
109 /* Ensures that all executions do not interfere with one another (single
110 * thread) and that they are apart from each other by at least what is
111 * defined as the schedulePeriod.
112 */
113 private ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(
114 groupedThreads("MastershipLoadBalancer", "%d", log));
Aaron Kruglikov29327982015-10-06 17:15:16 -0700115
116 @Activate
sangyun-han55e17982016-08-03 15:45:38 +0900117 public void activate(ComponentContext context) {
118 cfgService.registerProperties(getClass());
119 modified(context);
Aaron Kruglikov29327982015-10-06 17:15:16 -0700120 mastershipService.addListener(mastershipListener);
121 localId = clusterService.getLocalNode().id();
122 leadershipService.addListener(leadershipListener);
123 leadershipService.runForLeadership(REBALANCE_MASTERSHIP);
Victor Silva6b1c6d22016-09-20 17:00:59 -0300124 regionService.addListener(regionEventListener);
Aaron Kruglikov29327982015-10-06 17:15:16 -0700125 log.info("Started");
126 }
127
128 @Deactivate
129 public void deactivate() {
sangyun-han55e17982016-08-03 15:45:38 +0900130 cfgService.unregisterProperties(getClass(), false);
Aaron Kruglikov29327982015-10-06 17:15:16 -0700131 mastershipService.removeListener(mastershipListener);
132 leadershipService.withdraw(REBALANCE_MASTERSHIP);
133 leadershipService.removeListener(leadershipListener);
Victor Silva6b1c6d22016-09-20 17:00:59 -0300134 regionService.removeListener(regionEventListener);
Aaron Kruglikov29327982015-10-06 17:15:16 -0700135 cancelBalance();
136 executorService.shutdown();
137 log.info("Stopped");
138 }
139
sangyun-han55e17982016-08-03 15:45:38 +0900140 @Modified
141 public void modified(ComponentContext context) {
142 readComponentConfiguration(context);
143 cancelBalance();
144 scheduleBalance();
145 log.info("modified");
146 }
147
Madan Jampani620f70d2016-01-30 22:22:47 -0800148 private synchronized void processLeaderChange(NodeId newLeader) {
Aaron Kruglikov29327982015-10-06 17:15:16 -0700149 boolean currLeader = newLeader.equals(localId);
150 if (isLeader.getAndSet(currLeader) != currLeader) {
151 if (currLeader) {
152 scheduleBalance();
153 } else {
154 cancelBalance();
155 }
156 }
157 }
158
Victor Silva6b1c6d22016-09-20 17:00:59 -0300159 // Sets flag at execution to indicate there is currently a scheduled
160 // rebalancing. As soon as it starts running, the flag is set back to
161 // null and another rebalancing can be queued.
Aaron Kruglikov29327982015-10-06 17:15:16 -0700162 private void scheduleBalance() {
163 if (isLeader.get() && nextTask.get() == null) {
164
Victor Silva6b1c6d22016-09-20 17:00:59 -0300165 Future task = executorService.schedule(new BalanceTask(),
166 schedulePeriod, TimeUnit.SECONDS);
167
Aaron Kruglikov29327982015-10-06 17:15:16 -0700168 if (!nextTask.compareAndSet(null, task)) {
169 task.cancel(false);
170 }
171 }
172 }
173
Victor Silva6b1c6d22016-09-20 17:00:59 -0300174 private class BalanceTask implements Runnable {
175
176 @Override
177 public void run() {
178 // nextTask is now running, free the spot so that it is possible
179 // to queue up another upcoming task.
180 nextTask.set(null);
181
182 mastershipAdminService.balanceRoles();
183 log.info("Completed balance roles");
184 }
185 }
186
Aaron Kruglikov29327982015-10-06 17:15:16 -0700187 private void cancelBalance() {
188 Future task = nextTask.getAndSet(null);
189 if (task != null) {
190 task.cancel(false);
191 }
192 }
193
sangyun-han55e17982016-08-03 15:45:38 +0900194 /**
195 * Extracts properties from the component configuration context.
196 *
197 * @param context the component context
198 */
199 private void readComponentConfiguration(ComponentContext context) {
200 Dictionary<?, ?> properties = context.getProperties();
201
202 Integer newSchedulePeriod = Tools.getIntegerProperty(properties,
203 "schedulePeriod");
204 if (newSchedulePeriod == null) {
205 schedulePeriod = DEFAULT_SCHEDULE_PERIOD;
206 log.info("Schedule period is not configured, default value is {}",
207 DEFAULT_SCHEDULE_PERIOD);
208 } else {
209 schedulePeriod = newSchedulePeriod;
210 log.info("Configured. Schedule period is configured to {}", schedulePeriod);
211 }
212 }
213
Aaron Kruglikov29327982015-10-06 17:15:16 -0700214 private class InnerMastershipListener implements MastershipListener {
215
216 @Override
217 public void event(MastershipEvent event) {
Aaron Kruglikov29327982015-10-06 17:15:16 -0700218 scheduleBalance();
219 }
220 }
221
222 private class InnerLeadershipListener implements LeadershipEventListener {
223 @Override
224 public boolean isRelevant(LeadershipEvent event) {
225 return REBALANCE_MASTERSHIP.equals(event.subject().topic());
226 }
227
228 @Override
229 public void event(LeadershipEvent event) {
Madan Jampani620f70d2016-01-30 22:22:47 -0800230 processLeaderChange(event.subject().leaderNodeId());
Aaron Kruglikov29327982015-10-06 17:15:16 -0700231 }
232 }
Victor Silva6b1c6d22016-09-20 17:00:59 -0300233
234 private class InnerRegionListener implements RegionListener {
235 @Override
236 public void event(RegionEvent event) {
237 switch (event.type()) {
238 case REGION_MEMBERSHIP_CHANGED:
239 case REGION_UPDATED:
240 scheduleBalance();
241 break;
242 default:
243 break;
244 }
245 }
246 }
Madan Jampani620f70d2016-01-30 22:22:47 -0800247}