mirror of
https://github.com/opennetworkinglab/onos.git
synced 2025-10-29 15:21:52 +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