mirror of
https://github.com/opennetworkinglab/onos.git
synced 2025-10-28 06:41:19 +01:00
WIP: device mastership store based on leadership service.
Change-Id: I6347718f46b6600f93974825816fb537e39abb44
This commit is contained in:
parent
b9a91c134f
commit
84b6b40f8a
@ -0,0 +1,400 @@
|
||||
/*
|
||||
* Copyright 2015 Open Networking Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.onosproject.store.mastership.impl;
|
||||
|
||||
import static org.onlab.util.Tools.groupedThreads;
|
||||
import static org.onosproject.mastership.MastershipEvent.Type.BACKUPS_CHANGED;
|
||||
import static org.onosproject.mastership.MastershipEvent.Type.MASTER_CHANGED;
|
||||
import static org.slf4j.LoggerFactory.getLogger;
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.felix.scr.annotations.Activate;
|
||||
import org.apache.felix.scr.annotations.Component;
|
||||
import org.apache.felix.scr.annotations.Deactivate;
|
||||
import org.apache.felix.scr.annotations.Reference;
|
||||
import org.apache.felix.scr.annotations.ReferenceCardinality;
|
||||
import org.apache.felix.scr.annotations.Service;
|
||||
import org.onlab.util.KryoNamespace;
|
||||
import org.onosproject.cluster.ClusterService;
|
||||
import org.onosproject.cluster.Leadership;
|
||||
import org.onosproject.cluster.LeadershipEvent;
|
||||
import org.onosproject.cluster.LeadershipEventListener;
|
||||
import org.onosproject.cluster.LeadershipService;
|
||||
import org.onosproject.cluster.NodeId;
|
||||
import org.onosproject.cluster.RoleInfo;
|
||||
import org.onosproject.mastership.MastershipEvent;
|
||||
import org.onosproject.mastership.MastershipStore;
|
||||
import org.onosproject.mastership.MastershipStoreDelegate;
|
||||
import org.onosproject.mastership.MastershipTerm;
|
||||
import org.onosproject.net.DeviceId;
|
||||
import org.onosproject.net.MastershipRole;
|
||||
import org.onosproject.store.AbstractStore;
|
||||
import org.onosproject.store.cluster.messaging.ClusterCommunicationService;
|
||||
import org.onosproject.store.cluster.messaging.ClusterMessage;
|
||||
import org.onosproject.store.cluster.messaging.ClusterMessageHandler;
|
||||
import org.onosproject.store.cluster.messaging.MessageSubject;
|
||||
import org.onosproject.store.serializers.KryoNamespaces;
|
||||
import org.onosproject.store.serializers.KryoSerializer;
|
||||
import org.onosproject.store.serializers.StoreSerializer;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
/**
|
||||
* Implementation of the MastershipStore on top of Leadership Service.
|
||||
*/
|
||||
@Component(immediate = true, enabled = false)
|
||||
@Service
|
||||
public class ConsistentDeviceMastershipStore
|
||||
extends AbstractStore<MastershipEvent, MastershipStoreDelegate>
|
||||
implements MastershipStore {
|
||||
|
||||
private final Logger log = getLogger(getClass());
|
||||
|
||||
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
|
||||
protected LeadershipService leadershipService;
|
||||
|
||||
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
|
||||
protected ClusterService clusterService;
|
||||
|
||||
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
|
||||
protected ClusterCommunicationService clusterCommunicator;
|
||||
|
||||
private NodeId localNodeId;
|
||||
private final Set<DeviceId> connectedDevices = Sets.newHashSet();
|
||||
|
||||
private static final MessageSubject ROLE_QUERY_SUBJECT =
|
||||
new MessageSubject("mastership-store-device-role-query");
|
||||
private static final MessageSubject ROLE_RELINQUISH_SUBJECT =
|
||||
new MessageSubject("mastership-store-device-role-relinquish");
|
||||
|
||||
private static final Pattern DEVICE_MASTERSHIP_TOPIC_PATTERN =
|
||||
Pattern.compile("/devices/(.*)/mastership");
|
||||
|
||||
private static final long PEER_REQUEST_TIMEOUT_MS = 5000;
|
||||
private ExecutorService messageHandlingExecutor;
|
||||
private final LeadershipEventListener leadershipEventListener =
|
||||
new InternalDeviceMastershipEventListener();
|
||||
|
||||
private static final String NODE_ID_NULL = "Node ID cannot be null";
|
||||
private static final String DEVICE_ID_NULL = "Device ID cannot be null";;
|
||||
|
||||
public static final StoreSerializer SERIALIZER = new KryoSerializer() {
|
||||
@Override
|
||||
protected void setupKryoPool() {
|
||||
serializerPool = KryoNamespace.newBuilder()
|
||||
.register(KryoNamespaces.API)
|
||||
.register(MastershipRole.class)
|
||||
.register(MastershipEvent.class)
|
||||
.build();
|
||||
}
|
||||
};
|
||||
|
||||
@Activate
|
||||
public void activate() {
|
||||
messageHandlingExecutor =
|
||||
Executors.newSingleThreadExecutor(groupedThreads("onos/store/device/mastership", "message-handler"));
|
||||
clusterCommunicator.addSubscriber(ROLE_QUERY_SUBJECT,
|
||||
new RoleQueryHandler(),
|
||||
messageHandlingExecutor);
|
||||
clusterCommunicator.addSubscriber(ROLE_RELINQUISH_SUBJECT,
|
||||
new RoleRelinquishHandler(),
|
||||
messageHandlingExecutor);
|
||||
localNodeId = clusterService.getLocalNode().id();
|
||||
leadershipService.addListener(leadershipEventListener);
|
||||
|
||||
log.info("Started.");
|
||||
}
|
||||
|
||||
@Deactivate
|
||||
public void deactivate() {
|
||||
clusterCommunicator.removeSubscriber(ROLE_QUERY_SUBJECT);
|
||||
clusterCommunicator.removeSubscriber(ROLE_RELINQUISH_SUBJECT);
|
||||
messageHandlingExecutor.shutdown();
|
||||
leadershipService.removeListener(leadershipEventListener);
|
||||
|
||||
log.info("Stoppped.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public MastershipRole requestRole(DeviceId deviceId) {
|
||||
checkArgument(deviceId != null, DEVICE_ID_NULL);
|
||||
|
||||
String leadershipTopic = createDeviceMastershipTopic(deviceId);
|
||||
if (connectedDevices.add(deviceId)) {
|
||||
leadershipService.runForLeadership(leadershipTopic);
|
||||
return MastershipRole.STANDBY;
|
||||
} else {
|
||||
Leadership leadership = leadershipService.getLeadership(leadershipTopic);
|
||||
if (leadership != null && leadership.leader().equals(localNodeId)) {
|
||||
return MastershipRole.MASTER;
|
||||
} else {
|
||||
return MastershipRole.STANDBY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MastershipRole getRole(NodeId nodeId, DeviceId deviceId) {
|
||||
checkArgument(nodeId != null, NODE_ID_NULL);
|
||||
checkArgument(deviceId != null, DEVICE_ID_NULL);
|
||||
|
||||
String leadershipTopic = createDeviceMastershipTopic(deviceId);
|
||||
Leadership leadership = leadershipService.getLeadership(leadershipTopic);
|
||||
if (leadership != null && nodeId.equals(leadership.leader())) {
|
||||
return MastershipRole.MASTER;
|
||||
}
|
||||
|
||||
if (localNodeId.equals(nodeId)) {
|
||||
if (connectedDevices.contains(deviceId)) {
|
||||
return MastershipRole.STANDBY;
|
||||
} else {
|
||||
return MastershipRole.NONE;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
MastershipRole role = complete(clusterCommunicator.sendAndReceive(
|
||||
new ClusterMessage(
|
||||
localNodeId,
|
||||
ROLE_QUERY_SUBJECT,
|
||||
SERIALIZER.encode(deviceId)),
|
||||
nodeId));
|
||||
return role == null ? MastershipRole.NONE : role;
|
||||
} catch (IOException e) {
|
||||
log.warn("Failed to query {} for {}'s role. Defaulting to NONE", nodeId, deviceId, e);
|
||||
return MastershipRole.NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public NodeId getMaster(DeviceId deviceId) {
|
||||
checkArgument(deviceId != null, DEVICE_ID_NULL);
|
||||
|
||||
String leadershipTopic = createDeviceMastershipTopic(deviceId);
|
||||
Leadership leadership = leadershipService.getLeadership(leadershipTopic);
|
||||
return leadership != null ? leadership.leader() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoleInfo getNodes(DeviceId deviceId) {
|
||||
checkArgument(deviceId != null, DEVICE_ID_NULL);
|
||||
|
||||
Map<NodeId, MastershipRole> roles = Maps.newHashMap();
|
||||
clusterService
|
||||
.getNodes()
|
||||
.stream()
|
||||
.parallel()
|
||||
.forEach((node) -> roles.put(node.id(), getRole(node.id(), deviceId)));
|
||||
|
||||
NodeId master = null;
|
||||
final List<NodeId> standbys = Lists.newLinkedList();
|
||||
|
||||
for (Map.Entry<NodeId, MastershipRole> entry : roles.entrySet()) {
|
||||
if (entry.getValue() == MastershipRole.MASTER) {
|
||||
master = entry.getKey();
|
||||
} else if (entry.getValue() == MastershipRole.STANDBY) {
|
||||
standbys.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
return new RoleInfo(master, standbys);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<DeviceId> getDevices(NodeId nodeId) {
|
||||
checkArgument(nodeId != null, NODE_ID_NULL);
|
||||
|
||||
return leadershipService
|
||||
.ownedTopics(nodeId)
|
||||
.stream()
|
||||
.filter(this::isDeviceMastershipTopic)
|
||||
.map(this::extractDeviceIdFromTopic)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public MastershipEvent setMaster(NodeId nodeId, DeviceId deviceId) {
|
||||
checkArgument(nodeId != null, NODE_ID_NULL);
|
||||
checkArgument(deviceId != null, DEVICE_ID_NULL);
|
||||
|
||||
throw new UnsupportedOperationException("This operation is not supported in " + this.getClass().getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public MastershipTerm getTermFor(DeviceId deviceId) {
|
||||
checkArgument(deviceId != null, DEVICE_ID_NULL);
|
||||
|
||||
String leadershipTopic = createDeviceMastershipTopic(deviceId);
|
||||
Leadership leadership = leadershipService.getLeadership(leadershipTopic);
|
||||
return leadership != null ? MastershipTerm.of(leadership.leader(), leadership.epoch()) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MastershipEvent setStandby(NodeId nodeId, DeviceId deviceId) {
|
||||
checkArgument(nodeId != null, NODE_ID_NULL);
|
||||
checkArgument(deviceId != null, DEVICE_ID_NULL);
|
||||
|
||||
throw new UnsupportedOperationException("This operation is not supported in " + this.getClass().getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public MastershipEvent relinquishRole(NodeId nodeId, DeviceId deviceId) {
|
||||
checkArgument(nodeId != null, NODE_ID_NULL);
|
||||
checkArgument(deviceId != null, DEVICE_ID_NULL);
|
||||
|
||||
if (!nodeId.equals(localNodeId)) {
|
||||
log.debug("Forwarding request to relinquish "
|
||||
+ "role for device {} to {}", deviceId, nodeId);
|
||||
try {
|
||||
return complete(clusterCommunicator.sendAndReceive(
|
||||
new ClusterMessage(
|
||||
localNodeId,
|
||||
ROLE_RELINQUISH_SUBJECT,
|
||||
SERIALIZER.encode(deviceId)),
|
||||
nodeId));
|
||||
} catch (IOException e) {
|
||||
log.warn("Failed to send a request to relinquish role for {} to {}", deviceId, nodeId, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if this node is can be managed by this node.
|
||||
if (!connectedDevices.contains(deviceId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String leadershipTopic = createDeviceMastershipTopic(deviceId);
|
||||
Leadership currentLeadership = leadershipService.getLeadership(leadershipTopic);
|
||||
|
||||
MastershipEvent.Type eventType = null;
|
||||
if (currentLeadership != null && currentLeadership.leader().equals(localNodeId)) {
|
||||
eventType = MastershipEvent.Type.MASTER_CHANGED;
|
||||
} else {
|
||||
eventType = MastershipEvent.Type.BACKUPS_CHANGED;
|
||||
}
|
||||
|
||||
connectedDevices.remove(deviceId);
|
||||
leadershipService.withdraw(leadershipTopic);
|
||||
|
||||
return new MastershipEvent(eventType, deviceId, getNodes(deviceId));
|
||||
}
|
||||
|
||||
private class RoleQueryHandler implements ClusterMessageHandler {
|
||||
@Override
|
||||
public void handle(ClusterMessage message) {
|
||||
DeviceId deviceId = SERIALIZER.decode(message.payload());
|
||||
try {
|
||||
message.respond(SERIALIZER.encode(getRole(localNodeId, deviceId)));
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to responsd to role query", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void relinquishAllRole(NodeId nodeId) {
|
||||
// Noop. LeadershipService already takes care of detecting and purging deadlocks.
|
||||
}
|
||||
|
||||
private class RoleRelinquishHandler implements ClusterMessageHandler {
|
||||
@Override
|
||||
public void handle(ClusterMessage message) {
|
||||
DeviceId deviceId = SERIALIZER.decode(message.payload());
|
||||
try {
|
||||
message.respond(SERIALIZER.encode(relinquishRole(localNodeId, deviceId)));
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to relinquish role.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class InternalDeviceMastershipEventListener implements LeadershipEventListener {
|
||||
@Override
|
||||
public void event(LeadershipEvent event) {
|
||||
Leadership leadership = event.subject();
|
||||
if (!isDeviceMastershipTopic(leadership.topic())) {
|
||||
return;
|
||||
}
|
||||
NodeId nodeId = leadership.leader();
|
||||
DeviceId deviceId = extractDeviceIdFromTopic(leadership.topic());
|
||||
if (Objects.equal(nodeId, localNodeId) && connectedDevices.contains(deviceId)) {
|
||||
switch (event.type()) {
|
||||
case LEADER_ELECTED:
|
||||
notifyDelegate(new MastershipEvent(MASTER_CHANGED, deviceId, getNodes(deviceId)));
|
||||
break;
|
||||
case LEADER_REELECTED:
|
||||
// There is no concept of leader re-election in the new distributed leadership manager.
|
||||
throw new IllegalStateException("Unexpected event type");
|
||||
case LEADER_BOOTED:
|
||||
notifyDelegate(new MastershipEvent(BACKUPS_CHANGED, deviceId, getNodes(deviceId)));
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String createDeviceMastershipTopic(DeviceId deviceId) {
|
||||
return "/devices/" + deviceId.toString() + "/mastership";
|
||||
}
|
||||
|
||||
private DeviceId extractDeviceIdFromTopic(String topic) {
|
||||
Matcher m = DEVICE_MASTERSHIP_TOPIC_PATTERN.matcher(topic);
|
||||
if (m.matches()) {
|
||||
return DeviceId.deviceId(m.group(1));
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid device mastership topic: " + topic);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isDeviceMastershipTopic(String topic) {
|
||||
Matcher m = DEVICE_MASTERSHIP_TOPIC_PATTERN.matcher(topic);
|
||||
return m.matches();
|
||||
}
|
||||
|
||||
private <T> T complete(Future<byte[]> future) {
|
||||
try {
|
||||
return SERIALIZER.decode(future.get(PEER_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS));
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
log.error("Interrupted while waiting for operation to complete.", e);
|
||||
return null;
|
||||
} catch (TimeoutException | ExecutionException e) {
|
||||
log.error("Failed remote operation", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user