blob: 320c4d1a11367cb10424b5ee85ef01d4ceac79bf [file] [log] [blame]
Madan Jampani84b6b402015-02-25 17:49:54 -08001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2015-present Open Networking Laboratory
Madan Jampani84b6b402015-02-25 17:49:54 -08003 *
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 */
16package org.onosproject.store.mastership.impl;
17
18import static org.onlab.util.Tools.groupedThreads;
Madan Jampani0f6ad142015-05-13 17:10:04 -070019import static org.onosproject.mastership.MastershipEvent.Type.BACKUPS_CHANGED;
Madan Jampani84b6b402015-02-25 17:49:54 -080020import static org.onosproject.mastership.MastershipEvent.Type.MASTER_CHANGED;
21import static org.slf4j.LoggerFactory.getLogger;
22import static com.google.common.base.Preconditions.checkArgument;
23
Madan Jampani84b6b402015-02-25 17:49:54 -080024import java.util.List;
25import java.util.Map;
26import java.util.Set;
Madan Jampanif7536ab2015-05-07 23:23:23 -070027import java.util.concurrent.CompletableFuture;
Madan Jampani84b6b402015-02-25 17:49:54 -080028import java.util.concurrent.ExecutorService;
29import java.util.concurrent.Executors;
Madan Jampanif7536ab2015-05-07 23:23:23 -070030import java.util.concurrent.ScheduledExecutorService;
31import java.util.concurrent.TimeUnit;
Madan Jampani84b6b402015-02-25 17:49:54 -080032import java.util.regex.Matcher;
33import java.util.regex.Pattern;
34import java.util.stream.Collectors;
35
36import org.apache.felix.scr.annotations.Activate;
37import org.apache.felix.scr.annotations.Component;
38import org.apache.felix.scr.annotations.Deactivate;
39import org.apache.felix.scr.annotations.Reference;
40import org.apache.felix.scr.annotations.ReferenceCardinality;
41import org.apache.felix.scr.annotations.Service;
42import org.onlab.util.KryoNamespace;
43import org.onosproject.cluster.ClusterService;
44import org.onosproject.cluster.Leadership;
Madan Jampani620f70d2016-01-30 22:22:47 -080045import org.onosproject.cluster.LeadershipAdminService;
Madan Jampani84b6b402015-02-25 17:49:54 -080046import org.onosproject.cluster.LeadershipEvent;
47import org.onosproject.cluster.LeadershipEventListener;
48import org.onosproject.cluster.LeadershipService;
49import org.onosproject.cluster.NodeId;
50import org.onosproject.cluster.RoleInfo;
51import org.onosproject.mastership.MastershipEvent;
52import org.onosproject.mastership.MastershipStore;
53import org.onosproject.mastership.MastershipStoreDelegate;
54import org.onosproject.mastership.MastershipTerm;
55import org.onosproject.net.DeviceId;
56import org.onosproject.net.MastershipRole;
57import org.onosproject.store.AbstractStore;
58import org.onosproject.store.cluster.messaging.ClusterCommunicationService;
Madan Jampani84b6b402015-02-25 17:49:54 -080059import org.onosproject.store.cluster.messaging.MessageSubject;
60import org.onosproject.store.serializers.KryoNamespaces;
Madan Jampani84b6b402015-02-25 17:49:54 -080061import org.onosproject.store.serializers.StoreSerializer;
62import org.slf4j.Logger;
63
64import com.google.common.base.Objects;
Madan Jampani620f70d2016-01-30 22:22:47 -080065import com.google.common.collect.ImmutableList;
Madan Jampani84b6b402015-02-25 17:49:54 -080066import com.google.common.collect.Lists;
67import com.google.common.collect.Maps;
Madan Jampani84b6b402015-02-25 17:49:54 -080068
69/**
70 * Implementation of the MastershipStore on top of Leadership Service.
71 */
Madan Jampani5756c352015-04-29 00:23:58 -070072@Component(immediate = true, enabled = true)
Madan Jampani84b6b402015-02-25 17:49:54 -080073@Service
74public class ConsistentDeviceMastershipStore
75 extends AbstractStore<MastershipEvent, MastershipStoreDelegate>
76 implements MastershipStore {
77
78 private final Logger log = getLogger(getClass());
79
80 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
81 protected LeadershipService leadershipService;
82
83 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Madan Jampani620f70d2016-01-30 22:22:47 -080084 protected LeadershipAdminService leadershipAdminService;
85
86 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
Madan Jampani84b6b402015-02-25 17:49:54 -080087 protected ClusterService clusterService;
88
89 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
90 protected ClusterCommunicationService clusterCommunicator;
91
92 private NodeId localNodeId;
Madan Jampani84b6b402015-02-25 17:49:54 -080093
Madan Jampani84b6b402015-02-25 17:49:54 -080094 private static final MessageSubject ROLE_RELINQUISH_SUBJECT =
95 new MessageSubject("mastership-store-device-role-relinquish");
96
97 private static final Pattern DEVICE_MASTERSHIP_TOPIC_PATTERN =
Madan Jampani5756c352015-04-29 00:23:58 -070098 Pattern.compile("device:(.*)");
Madan Jampani84b6b402015-02-25 17:49:54 -080099
Madan Jampani84b6b402015-02-25 17:49:54 -0800100 private ExecutorService messageHandlingExecutor;
Madan Jampanif7536ab2015-05-07 23:23:23 -0700101 private ScheduledExecutorService transferExecutor;
Madan Jampani84b6b402015-02-25 17:49:54 -0800102 private final LeadershipEventListener leadershipEventListener =
103 new InternalDeviceMastershipEventListener();
104
105 private static final String NODE_ID_NULL = "Node ID cannot be null";
Madan Jampanif7536ab2015-05-07 23:23:23 -0700106 private static final String DEVICE_ID_NULL = "Device ID cannot be null";
107 private static final int WAIT_BEFORE_MASTERSHIP_HANDOFF_MILLIS = 3000;
Madan Jampani84b6b402015-02-25 17:49:54 -0800108
HIGUCHI Yutae7290652016-05-18 11:29:01 -0700109 public static final StoreSerializer SERIALIZER = StoreSerializer.using(
110 KryoNamespace.newBuilder()
Madan Jampani84b6b402015-02-25 17:49:54 -0800111 .register(KryoNamespaces.API)
112 .register(MastershipRole.class)
113 .register(MastershipEvent.class)
Madan Jampani1af8e132015-04-30 16:41:18 -0700114 .register(MastershipEvent.Type.class)
HIGUCHI Yutae7290652016-05-18 11:29:01 -0700115 .build("MastershipStore"));
Madan Jampani84b6b402015-02-25 17:49:54 -0800116
117 @Activate
118 public void activate() {
119 messageHandlingExecutor =
Madan Jampanif7536ab2015-05-07 23:23:23 -0700120 Executors.newSingleThreadExecutor(
HIGUCHI Yuta060da9a2016-03-11 19:16:35 -0800121 groupedThreads("onos/store/device/mastership", "message-handler", log));
Madan Jampanif7536ab2015-05-07 23:23:23 -0700122 transferExecutor =
123 Executors.newSingleThreadScheduledExecutor(
HIGUCHI Yuta060da9a2016-03-11 19:16:35 -0800124 groupedThreads("onos/store/device/mastership", "mastership-transfer-executor", log));
Madan Jampanibbed40422015-05-20 12:00:38 -0700125 clusterCommunicator.addSubscriber(ROLE_RELINQUISH_SUBJECT,
Madan Jampanid46e18f2015-05-04 23:19:33 -0700126 SERIALIZER::decode,
Madan Jampanif7536ab2015-05-07 23:23:23 -0700127 this::relinquishLocalRole,
Madan Jampanid46e18f2015-05-04 23:19:33 -0700128 SERIALIZER::encode,
129 messageHandlingExecutor);
Madan Jampani84b6b402015-02-25 17:49:54 -0800130 localNodeId = clusterService.getLocalNode().id();
131 leadershipService.addListener(leadershipEventListener);
132
Madan Jampaniafeebbd2015-05-19 15:26:01 -0700133 log.info("Started");
Madan Jampani84b6b402015-02-25 17:49:54 -0800134 }
135
136 @Deactivate
137 public void deactivate() {
Madan Jampani84b6b402015-02-25 17:49:54 -0800138 clusterCommunicator.removeSubscriber(ROLE_RELINQUISH_SUBJECT);
139 messageHandlingExecutor.shutdown();
Madan Jampanif7536ab2015-05-07 23:23:23 -0700140 transferExecutor.shutdown();
Madan Jampani84b6b402015-02-25 17:49:54 -0800141 leadershipService.removeListener(leadershipEventListener);
142
Madan Jampaniafeebbd2015-05-19 15:26:01 -0700143 log.info("Stopped");
Madan Jampani84b6b402015-02-25 17:49:54 -0800144 }
145
146 @Override
Madan Jampanide003d92015-05-11 17:14:20 -0700147 public CompletableFuture<MastershipRole> requestRole(DeviceId deviceId) {
Madan Jampani84b6b402015-02-25 17:49:54 -0800148 checkArgument(deviceId != null, DEVICE_ID_NULL);
149
150 String leadershipTopic = createDeviceMastershipTopic(deviceId);
Madan Jampani620f70d2016-01-30 22:22:47 -0800151 Leadership leadership = leadershipService.runForLeadership(leadershipTopic);
152 return CompletableFuture.completedFuture(localNodeId.equals(leadership.leaderNodeId())
153 ? MastershipRole.MASTER : MastershipRole.STANDBY);
Madan Jampani84b6b402015-02-25 17:49:54 -0800154 }
155
156 @Override
157 public MastershipRole getRole(NodeId nodeId, DeviceId deviceId) {
158 checkArgument(nodeId != null, NODE_ID_NULL);
159 checkArgument(deviceId != null, DEVICE_ID_NULL);
160
161 String leadershipTopic = createDeviceMastershipTopic(deviceId);
Madan Jampani620f70d2016-01-30 22:22:47 -0800162 Leadership leadership = leadershipService.getLeadership(leadershipTopic);
163 NodeId leader = leadership == null ? null : leadership.leaderNodeId();
164 List<NodeId> candidates = leadership == null ?
165 ImmutableList.of() : ImmutableList.copyOf(leadership.candidates());
166 return Objects.equal(nodeId, leader) ?
167 MastershipRole.MASTER : candidates.contains(nodeId) ? MastershipRole.STANDBY : MastershipRole.NONE;
Madan Jampani84b6b402015-02-25 17:49:54 -0800168 }
169
170 @Override
171 public NodeId getMaster(DeviceId deviceId) {
172 checkArgument(deviceId != null, DEVICE_ID_NULL);
173
Madan Jampani620f70d2016-01-30 22:22:47 -0800174 return leadershipService.getLeader(createDeviceMastershipTopic(deviceId));
Madan Jampani84b6b402015-02-25 17:49:54 -0800175 }
176
177 @Override
178 public RoleInfo getNodes(DeviceId deviceId) {
179 checkArgument(deviceId != null, DEVICE_ID_NULL);
180
181 Map<NodeId, MastershipRole> roles = Maps.newHashMap();
Madan Jampani620f70d2016-01-30 22:22:47 -0800182 clusterService.getNodes()
183 .forEach((node) -> roles.put(node.id(), getRole(node.id(), deviceId)));
Madan Jampani84b6b402015-02-25 17:49:54 -0800184
185 NodeId master = null;
186 final List<NodeId> standbys = Lists.newLinkedList();
187
Madan Jampani86940d92015-05-06 11:47:57 -0700188 List<NodeId> candidates = leadershipService.getCandidates(createDeviceMastershipTopic(deviceId));
189
Madan Jampani84b6b402015-02-25 17:49:54 -0800190 for (Map.Entry<NodeId, MastershipRole> entry : roles.entrySet()) {
191 if (entry.getValue() == MastershipRole.MASTER) {
192 master = entry.getKey();
193 } else if (entry.getValue() == MastershipRole.STANDBY) {
194 standbys.add(entry.getKey());
195 }
196 }
197
Madan Jampani86940d92015-05-06 11:47:57 -0700198 List<NodeId> sortedStandbyList = candidates.stream().filter(standbys::contains).collect(Collectors.toList());
199
200 return new RoleInfo(master, sortedStandbyList);
Madan Jampani84b6b402015-02-25 17:49:54 -0800201 }
202
203 @Override
204 public Set<DeviceId> getDevices(NodeId nodeId) {
205 checkArgument(nodeId != null, NODE_ID_NULL);
206
207 return leadershipService
208 .ownedTopics(nodeId)
209 .stream()
210 .filter(this::isDeviceMastershipTopic)
211 .map(this::extractDeviceIdFromTopic)
212 .collect(Collectors.toSet());
213 }
214
215 @Override
Madan Jampanif7536ab2015-05-07 23:23:23 -0700216 public CompletableFuture<MastershipEvent> setMaster(NodeId nodeId, DeviceId deviceId) {
Madan Jampani84b6b402015-02-25 17:49:54 -0800217 checkArgument(nodeId != null, NODE_ID_NULL);
218 checkArgument(deviceId != null, DEVICE_ID_NULL);
219
Madan Jampani620f70d2016-01-30 22:22:47 -0800220 String leadershipTopic = createDeviceMastershipTopic(deviceId);
221 if (leadershipAdminService.promoteToTopOfCandidateList(leadershipTopic, nodeId)) {
222 transferExecutor.schedule(() -> leadershipAdminService.transferLeadership(leadershipTopic, nodeId),
223 WAIT_BEFORE_MASTERSHIP_HANDOFF_MILLIS, TimeUnit.MILLISECONDS);
Madan Jampani1af8e132015-04-30 16:41:18 -0700224 }
Madan Jampanif7536ab2015-05-07 23:23:23 -0700225 return CompletableFuture.completedFuture(null);
Madan Jampani84b6b402015-02-25 17:49:54 -0800226 }
227
228 @Override
229 public MastershipTerm getTermFor(DeviceId deviceId) {
230 checkArgument(deviceId != null, DEVICE_ID_NULL);
231
232 String leadershipTopic = createDeviceMastershipTopic(deviceId);
233 Leadership leadership = leadershipService.getLeadership(leadershipTopic);
Madan Jampanidbe8a812016-01-31 21:10:46 -0800234 return leadership != null && leadership.leaderNodeId() != null ?
235 MastershipTerm.of(leadership.leaderNodeId(), leadership.leader().term()) : null;
Madan Jampani84b6b402015-02-25 17:49:54 -0800236 }
237
238 @Override
Madan Jampanif7536ab2015-05-07 23:23:23 -0700239 public CompletableFuture<MastershipEvent> setStandby(NodeId nodeId, DeviceId deviceId) {
Madan Jampani84b6b402015-02-25 17:49:54 -0800240 checkArgument(nodeId != null, NODE_ID_NULL);
241 checkArgument(deviceId != null, DEVICE_ID_NULL);
242
Madan Jampani1af8e132015-04-30 16:41:18 -0700243 NodeId currentMaster = getMaster(deviceId);
244 if (!nodeId.equals(currentMaster)) {
Madan Jampanif7536ab2015-05-07 23:23:23 -0700245 return CompletableFuture.completedFuture(null);
Madan Jampani1af8e132015-04-30 16:41:18 -0700246 }
Madan Jampanid46e18f2015-05-04 23:19:33 -0700247
248 String leadershipTopic = createDeviceMastershipTopic(deviceId);
249 List<NodeId> candidates = leadershipService.getCandidates(leadershipTopic);
250
251 NodeId newMaster = candidates.stream()
252 .filter(candidate -> !Objects.equal(nodeId, candidate))
253 .findFirst()
254 .orElse(null);
255 log.info("Transitioning to role {} for {}. Next master: {}",
256 newMaster != null ? MastershipRole.STANDBY : MastershipRole.NONE, deviceId, newMaster);
257
258 if (newMaster != null) {
259 return setMaster(newMaster, deviceId);
260 }
261 return relinquishRole(nodeId, deviceId);
Madan Jampani84b6b402015-02-25 17:49:54 -0800262 }
263
264 @Override
Madan Jampanif7536ab2015-05-07 23:23:23 -0700265 public CompletableFuture<MastershipEvent> relinquishRole(NodeId nodeId, DeviceId deviceId) {
Madan Jampani84b6b402015-02-25 17:49:54 -0800266 checkArgument(nodeId != null, NODE_ID_NULL);
267 checkArgument(deviceId != null, DEVICE_ID_NULL);
268
Madan Jampanibbed40422015-05-20 12:00:38 -0700269 if (nodeId.equals(localNodeId)) {
270 return relinquishLocalRole(deviceId);
Madan Jampani84b6b402015-02-25 17:49:54 -0800271 }
Madan Jampanibbed40422015-05-20 12:00:38 -0700272
273 log.debug("Forwarding request to relinquish "
274 + "role for device {} to {}", deviceId, nodeId);
275 return clusterCommunicator.sendAndReceive(
276 deviceId,
277 ROLE_RELINQUISH_SUBJECT,
278 SERIALIZER::encode,
279 SERIALIZER::decode,
280 nodeId);
Madan Jampanif7536ab2015-05-07 23:23:23 -0700281 }
282
Madan Jampanibbed40422015-05-20 12:00:38 -0700283 private CompletableFuture<MastershipEvent> relinquishLocalRole(DeviceId deviceId) {
Madan Jampanif7536ab2015-05-07 23:23:23 -0700284 checkArgument(deviceId != null, DEVICE_ID_NULL);
Madan Jampani84b6b402015-02-25 17:49:54 -0800285
Madan Jampani620f70d2016-01-30 22:22:47 -0800286 String leadershipTopic = createDeviceMastershipTopic(deviceId);
287 if (!leadershipService.getCandidates(leadershipTopic).contains(localNodeId)) {
Madan Jampanibbed40422015-05-20 12:00:38 -0700288 return CompletableFuture.completedFuture(null);
Madan Jampani84b6b402015-02-25 17:49:54 -0800289 }
Madan Jampani620f70d2016-01-30 22:22:47 -0800290 MastershipEvent.Type eventType = localNodeId.equals(leadershipService.getLeader(leadershipTopic)) ?
291 MastershipEvent.Type.MASTER_CHANGED : MastershipEvent.Type.BACKUPS_CHANGED;
292 leadershipService.withdraw(leadershipTopic);
293 return CompletableFuture.completedFuture(new MastershipEvent(eventType, deviceId, getNodes(deviceId)));
Madan Jampani1af8e132015-04-30 16:41:18 -0700294 }
295
Madan Jampani84b6b402015-02-25 17:49:54 -0800296 @Override
297 public void relinquishAllRole(NodeId nodeId) {
Madan Jampani620f70d2016-01-30 22:22:47 -0800298 // Noop. LeadershipService already takes care of detecting and purging stale locks.
Madan Jampani84b6b402015-02-25 17:49:54 -0800299 }
300
Madan Jampani84b6b402015-02-25 17:49:54 -0800301 private class InternalDeviceMastershipEventListener implements LeadershipEventListener {
Madan Jampani620f70d2016-01-30 22:22:47 -0800302
303 @Override
304 public boolean isRelevant(LeadershipEvent event) {
305 Leadership leadership = event.subject();
306 return isDeviceMastershipTopic(leadership.topic());
307 }
308
Madan Jampani84b6b402015-02-25 17:49:54 -0800309 @Override
310 public void event(LeadershipEvent event) {
311 Leadership leadership = event.subject();
Madan Jampani84b6b402015-02-25 17:49:54 -0800312 DeviceId deviceId = extractDeviceIdFromTopic(leadership.topic());
Madan Jampani620f70d2016-01-30 22:22:47 -0800313 RoleInfo roleInfo = getNodes(deviceId);
Thomas Vachuska4b839c72015-05-18 15:43:03 -0700314 switch (event.type()) {
Madan Jampani620f70d2016-01-30 22:22:47 -0800315 case LEADER_AND_CANDIDATES_CHANGED:
316 notifyDelegate(new MastershipEvent(BACKUPS_CHANGED, deviceId, roleInfo));
317 notifyDelegate(new MastershipEvent(MASTER_CHANGED, deviceId, roleInfo));
Thomas Vachuska4b839c72015-05-18 15:43:03 -0700318 break;
Madan Jampani620f70d2016-01-30 22:22:47 -0800319 case LEADER_CHANGED:
320 notifyDelegate(new MastershipEvent(MASTER_CHANGED, deviceId, roleInfo));
Thomas Vachuska4b839c72015-05-18 15:43:03 -0700321 break;
322 case CANDIDATES_CHANGED:
Madan Jampani620f70d2016-01-30 22:22:47 -0800323 notifyDelegate(new MastershipEvent(BACKUPS_CHANGED, deviceId, roleInfo));
Thomas Vachuska4b839c72015-05-18 15:43:03 -0700324 break;
325 default:
326 return;
Madan Jampani84b6b402015-02-25 17:49:54 -0800327 }
328 }
329 }
330
331 private String createDeviceMastershipTopic(DeviceId deviceId) {
Madan Jampani5756c352015-04-29 00:23:58 -0700332 return String.format("device:%s", deviceId.toString());
Madan Jampani84b6b402015-02-25 17:49:54 -0800333 }
334
335 private DeviceId extractDeviceIdFromTopic(String topic) {
336 Matcher m = DEVICE_MASTERSHIP_TOPIC_PATTERN.matcher(topic);
337 if (m.matches()) {
338 return DeviceId.deviceId(m.group(1));
339 } else {
340 throw new IllegalArgumentException("Invalid device mastership topic: " + topic);
341 }
342 }
343
344 private boolean isDeviceMastershipTopic(String topic) {
345 Matcher m = DEVICE_MASTERSHIP_TOPIC_PATTERN.matcher(topic);
346 return m.matches();
347 }
Madan Jampanidbe8a812016-01-31 21:10:46 -0800348}