/**
* Copyright (c) 2016, 2017 Pantheon Technologies s.r.o. and others. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*/
package org.opendaylight.openflowplugin.applications.frm.impl;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.Nonnull;
import org.opendaylight.controller.md.sal.binding.api.ClusteredDataTreeChangeListener;
import org.opendaylight.controller.md.sal.binding.api.DataBroker;
import org.opendaylight.controller.md.sal.binding.api.DataObjectModification;
import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier;
import org.opendaylight.controller.md.sal.binding.api.DataTreeModification;
import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
import org.opendaylight.controller.sal.binding.api.NotificationProviderService;
import org.opendaylight.mdsal.singleton.common.api.ClusterSingletonServiceProvider;
import org.opendaylight.openflowplugin.applications.frm.FlowNodeReconciliation;
import org.opendaylight.openflowplugin.common.wait.SimpleTaskRetryLooper;
import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode;
import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorRemoved;
import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorUpdated;
import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeRemoved;
import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeUpdated;
import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes;
import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.OpendaylightInventoryListener;
import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node;
import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey;
import org.opendaylight.yangtools.concepts.ListenerRegistration;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Manager for clustering service registrations of {@link DeviceMastership}.
*/
public class DeviceMastershipManager implements ClusteredDataTreeChangeListener<FlowCapableNode>,
OpendaylightInventoryListener, AutoCloseable{
private static final Logger LOG = LoggerFactory.getLogger(DeviceMastershipManager.class);
private static final InstanceIdentifier<FlowCapableNode> II_TO_FLOW_CAPABLE_NODE
= InstanceIdentifier.builder(Nodes.class)
.child(Node.class)
.augmentation(FlowCapableNode.class)
.build();
private final ClusterSingletonServiceProvider clusterSingletonService;
private final ListenerRegistration<?> notifListenerRegistration;
private final FlowNodeReconciliation reconcliationAgent;
private final DataBroker dataBroker;
private final ConcurrentHashMap<NodeId, DeviceMastership> deviceMasterships = new ConcurrentHashMap();
private final Object lockObj = new Object();
private ListenerRegistration<DeviceMastershipManager> listenerRegistration;
private Set<InstanceIdentifier<FlowCapableNode>> activeNodes = Collections.emptySet();
public DeviceMastershipManager(final ClusterSingletonServiceProvider clusterSingletonService,
final NotificationProviderService notificationService,
final FlowNodeReconciliation reconcliationAgent,
final DataBroker dataBroker) {
this.clusterSingletonService = clusterSingletonService;
this.notifListenerRegistration = notificationService.registerNotificationListener(this);
this.reconcliationAgent = reconcliationAgent;
this.dataBroker = dataBroker;
registerNodeListener();
}
public boolean isDeviceMastered(final NodeId nodeId) {
return deviceMasterships.get(nodeId) != null && deviceMasterships.get(nodeId).isDeviceMastered();
}
public boolean isNodeActive(final NodeId nodeId) {
final InstanceIdentifier<FlowCapableNode> flowNodeIdentifier = InstanceIdentifier.create(Nodes.class)
.child(Node.class, new NodeKey(nodeId)).augmentation(FlowCapableNode.class);
return activeNodes.contains(flowNodeIdentifier);
}
@VisibleForTesting
ConcurrentHashMap<NodeId, DeviceMastership> getDeviceMasterships() {
return deviceMasterships;
}
/**
* Temporary solution before Mastership manager from plugin.
* Remove notification after update.
* Update node notification should be send only when mastership in plugin was granted.
* @param notification received notification
*/
@Override
public void onNodeUpdated(NodeUpdated notification) {
LOG.debug("NodeUpdate notification received : {}", notification);
DeviceMastership membership = deviceMasterships.computeIfAbsent(notification.getId(), device ->
new DeviceMastership(notification.getId(), reconcliationAgent));
membership.reconcile();
}
@Override
public void onNodeConnectorUpdated(NodeConnectorUpdated notification) {
//Not published by plugin
}
@Override
public void onNodeRemoved(NodeRemoved notification) {
LOG.debug("NodeRemoved notification received : {}", notification);
NodeId nodeId = notification.getNodeRef().getValue().firstKeyOf(Node.class).getId();
final DeviceMastership mastership = deviceMasterships.remove(nodeId);
if (mastership != null) {
mastership.close();
LOG.info("Unregistered FRM cluster singleton service for service id : {}", nodeId.getValue());
}
}
@Override
public void onNodeConnectorRemoved(NodeConnectorRemoved notification) {
//Not published by plugin
}
@Override
public void onDataTreeChanged(@Nonnull Collection<DataTreeModification<FlowCapableNode>> changes) {
Preconditions.checkNotNull(changes, "Changes may not be null!");
for (DataTreeModification<FlowCapableNode> change : changes) {
final InstanceIdentifier<FlowCapableNode> key = change.getRootPath().getRootIdentifier();
final DataObjectModification<FlowCapableNode> mod = change.getRootNode();
final InstanceIdentifier<FlowCapableNode> nodeIdent =
key.firstIdentifierOf(FlowCapableNode.class);
switch (mod.getModificationType()) {
case DELETE:
if (mod.getDataAfter() == null) {
remove(key, mod.getDataBefore(), nodeIdent);
}
break;
case SUBTREE_MODIFIED:
//NO-OP since we do not need to reconcile on Node-updated
break;
case WRITE:
if (mod.getDataBefore() == null) {
add(key, mod.getDataAfter(), nodeIdent);
}
break;
default:
throw new IllegalArgumentException("Unhandled modification type " + mod.getModificationType());
}
}
}
public void remove(InstanceIdentifier<FlowCapableNode> identifier, FlowCapableNode del,
InstanceIdentifier<FlowCapableNode> nodeIdent) {
if(compareInstanceIdentifierTail(identifier,II_TO_FLOW_CAPABLE_NODE)){
if (LOG.isDebugEnabled()) {
LOG.debug("Node removed: {}",nodeIdent.firstKeyOf(Node.class).getId().getValue());
}
if ( ! nodeIdent.isWildcarded()) {
if (activeNodes.contains(nodeIdent)) {
synchronized (lockObj) {
if (activeNodes.contains(nodeIdent)) {
Set<InstanceIdentifier<FlowCapableNode>> set =
Sets.newHashSet(activeNodes);
set.remove(nodeIdent);
activeNodes = Collections.unmodifiableSet(set);
setNodeOperationalStatus(nodeIdent,false);
}
}
}
}
}
}
public void add(InstanceIdentifier<FlowCapableNode> identifier, FlowCapableNode add,
InstanceIdentifier<FlowCapableNode> nodeIdent) {
if(compareInstanceIdentifierTail(identifier,II_TO_FLOW_CAPABLE_NODE)){
if (LOG.isDebugEnabled()) {
LOG.debug("Node added: {}",nodeIdent.firstKeyOf(Node.class).getId().getValue());
}
if ( ! nodeIdent.isWildcarded()) {
if (!activeNodes.contains(nodeIdent)) {
synchronized (lockObj) {
if (!activeNodes.contains(nodeIdent)) {
Set<InstanceIdentifier<FlowCapableNode>> set = Sets.newHashSet(activeNodes);
set.add(nodeIdent);
activeNodes = Collections.unmodifiableSet(set);
setNodeOperationalStatus(nodeIdent,true);
}
}
}
}
}
}
@Override
public void close() {
if (listenerRegistration != null) {
try {
listenerRegistration.close();
} catch (Exception e) {
LOG.warn("Error occurred while closing operational Node listener: {}", e.getMessage());
LOG.debug("Error occurred while closing operational Node listener", e);
}
listenerRegistration = null;
}
if (notifListenerRegistration != null) {
notifListenerRegistration.close();
}
}
private boolean compareInstanceIdentifierTail(InstanceIdentifier<?> identifier1,
InstanceIdentifier<?> identifier2) {
return Iterables.getLast(identifier1.getPathArguments()).equals(Iterables.getLast(identifier2.getPathArguments()));
}
private void setNodeOperationalStatus(InstanceIdentifier<FlowCapableNode> nodeIid, boolean status) {
NodeId nodeId = nodeIid.firstKeyOf(Node.class).getId();
if (nodeId != null ) {
if (deviceMasterships.containsKey(nodeId) ) {
deviceMasterships.get(nodeId).setDeviceOperationalStatus(status);
LOG.debug("Operational status of device {} is set to {}",nodeId, status);
}
}
}
private void registerNodeListener(){
final InstanceIdentifier<FlowCapableNode> flowNodeWildCardIdentifier = InstanceIdentifier.create(Nodes.class)
.child(Node.class).augmentation(FlowCapableNode.class);
final DataTreeIdentifier<FlowCapableNode> treeId =
new DataTreeIdentifier<>(LogicalDatastoreType.OPERATIONAL, flowNodeWildCardIdentifier);
try {
SimpleTaskRetryLooper looper = new SimpleTaskRetryLooper(ForwardingRulesManagerImpl.STARTUP_LOOP_TICK,
ForwardingRulesManagerImpl.STARTUP_LOOP_MAX_RETRIES);
listenerRegistration = looper.loopUntilNoException(() ->
dataBroker.registerDataTreeChangeListener(treeId, DeviceMastershipManager.this));
} catch (Exception e) {
LOG.warn("Data listener registration failed: {}", e.getMessage());
LOG.debug("Data listener registration failed ", e);
throw new IllegalStateException("Node listener registration failed!", e);
}
}
}