blob: 4eeca07b75894901d16a3de9430bbb4872a6fb15 [file] [log] [blame]
Aaron Kruglikov29327982015-10-06 17:15:16 -07001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2015-present Open Networking Laboratory
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
19import com.google.common.util.concurrent.ListenableScheduledFuture;
20import com.google.common.util.concurrent.ListeningScheduledExecutorService;
21import com.google.common.util.concurrent.MoreExecutors;
Madan Jampani620f70d2016-01-30 22:22:47 -080022
Aaron Kruglikov29327982015-10-06 17:15:16 -070023import org.apache.felix.scr.annotations.Activate;
24import org.apache.felix.scr.annotations.Component;
25import org.apache.felix.scr.annotations.Deactivate;
26import org.apache.felix.scr.annotations.Reference;
27import org.apache.felix.scr.annotations.ReferenceCardinality;
28import 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;
37import org.slf4j.Logger;
38
39import java.util.concurrent.Executors;
40import java.util.concurrent.Future;
41import java.util.concurrent.TimeUnit;
42import java.util.concurrent.atomic.AtomicBoolean;
43import java.util.concurrent.atomic.AtomicReference;
44
45import static org.slf4j.LoggerFactory.getLogger;
46
47/**
48 * An app to perform automatic load balancing in response to events. Load balancing events are triggered by any
49 * change in mastership and are limited to a frequency of one every 30 seconds, all load balancing is run on an outside
50 * thread executor that must only have one thread due to issues that can occur is multiple balancing events occur in
51 * parallel.
52 */
53@Component(immediate = true)
54public class MastershipLoadBalancer {
55
56 private final Logger log = getLogger(getClass());
57
Simon Hunt89431db2016-03-08 15:02:43 -080058 // TODO: parameterize via component config
59 private static final int SCHEDULE_PERIOD = 5;
Aaron Kruglikov29327982015-10-06 17:15:16 -070060 private static final String REBALANCE_MASTERSHIP = "rebalance/mastership";
61
62 private NodeId localId;
63
64 private AtomicBoolean isLeader = new AtomicBoolean(false);
65
66 private AtomicReference<Future> nextTask = new AtomicReference<>();
67
68 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
69 protected MastershipService mastershipService;
70
71 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
72 protected MastershipAdminService mastershipAdminService;
73
74 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
75 protected LeadershipService leadershipService;
76
77 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
78 protected ClusterService clusterService;
79
80 private InnerLeadershipListener leadershipListener = new InnerLeadershipListener();
81
82 /* This listener is used to trigger balancing for any mastership event which will include switches changing state
83 between active and inactive states as well as the same variety of event occurring with ONOS nodes. Must
84 use a listenable executor to ensure events are triggered with no frequency greater than once every 30 seconds.
85 */
86 private InnerMastershipListener mastershipListener = new InnerMastershipListener();
87
88 //Ensures that all executions do not interfere with one another (single thread)
89 private ListeningScheduledExecutorService executorService = MoreExecutors.
90 listeningDecorator(Executors.newSingleThreadScheduledExecutor());
91
92 @Activate
93 public void activate() {
94 mastershipService.addListener(mastershipListener);
95 localId = clusterService.getLocalNode().id();
96 leadershipService.addListener(leadershipListener);
97 leadershipService.runForLeadership(REBALANCE_MASTERSHIP);
98 log.info("Started");
99 }
100
101 @Deactivate
102 public void deactivate() {
103 mastershipService.removeListener(mastershipListener);
104 leadershipService.withdraw(REBALANCE_MASTERSHIP);
105 leadershipService.removeListener(leadershipListener);
106 cancelBalance();
107 executorService.shutdown();
108 log.info("Stopped");
109 }
110
Madan Jampani620f70d2016-01-30 22:22:47 -0800111 private synchronized void processLeaderChange(NodeId newLeader) {
Aaron Kruglikov29327982015-10-06 17:15:16 -0700112 boolean currLeader = newLeader.equals(localId);
113 if (isLeader.getAndSet(currLeader) != currLeader) {
114 if (currLeader) {
115 scheduleBalance();
116 } else {
117 cancelBalance();
118 }
119 }
120 }
121
122 private void scheduleBalance() {
123 if (isLeader.get() && nextTask.get() == null) {
124
Simon Hunt89431db2016-03-08 15:02:43 -0800125 ListenableScheduledFuture task =
126 executorService.schedule(mastershipAdminService::balanceRoles,
127 SCHEDULE_PERIOD, TimeUnit.SECONDS);
Aaron Kruglikov29327982015-10-06 17:15:16 -0700128 task.addListener(() -> {
129 log.info("Completed balance roles");
130 nextTask.set(null);
131 }, MoreExecutors.directExecutor()
132 );
133 if (!nextTask.compareAndSet(null, task)) {
134 task.cancel(false);
135 }
136 }
137 }
138
139 private void cancelBalance() {
140 Future task = nextTask.getAndSet(null);
141 if (task != null) {
142 task.cancel(false);
143 }
144 }
145
146 private class InnerMastershipListener implements MastershipListener {
147
148 @Override
149 public void event(MastershipEvent event) {
150 //Sets flag at execution to indicate there is currently a scheduled rebalancing, reverts upon completion
151 scheduleBalance();
152 }
153 }
154
155 private class InnerLeadershipListener implements LeadershipEventListener {
156 @Override
157 public boolean isRelevant(LeadershipEvent event) {
158 return REBALANCE_MASTERSHIP.equals(event.subject().topic());
159 }
160
161 @Override
162 public void event(LeadershipEvent event) {
Madan Jampani620f70d2016-01-30 22:22:47 -0800163 processLeaderChange(event.subject().leaderNodeId());
Aaron Kruglikov29327982015-10-06 17:15:16 -0700164 }
165 }
Madan Jampani620f70d2016-01-30 22:22:47 -0800166}