/*
* Copyright © 2016 Red Hat, Inc. 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.ovsdb.southbound;
import java.net.ConnectException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
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.DataTreeChangeListener;
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.ovsdb.lib.OvsdbClient;
import org.opendaylight.ovsdb.southbound.ovsdb.transact.BridgeOperationalState;
import org.opendaylight.ovsdb.southbound.ovsdb.transact.TransactCommandAggregator;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.OvsdbBridgeAugmentation;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.OvsdbNodeAugmentation;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.ovsdb.rev150105.ovsdb.node.attributes.ConnectionInfo;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyKey;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
import org.opendaylight.yangtools.concepts.ListenerRegistration;
import org.opendaylight.yangtools.yang.binding.Augmentation;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Data-tree change listener for OVSDB.
*/
public class OvsdbDataTreeChangeListener implements ClusteredDataTreeChangeListener<Node>, AutoCloseable {
/** Our registration. */
private final ListenerRegistration<DataTreeChangeListener<Node>> registration;
/** The connection manager. */
private final OvsdbConnectionManager cm;
/** The data broker. */
private final DataBroker db;
/** The instance identifier codec. */
private final InstanceIdentifierCodec instanceIdentifierCodec;
/** Logger. */
private static final Logger LOG = LoggerFactory.getLogger(OvsdbDataTreeChangeListener.class);
/**
* Create an instance and register the listener.
*
* @param db The data broker.
* @param cm The connection manager.
*/
OvsdbDataTreeChangeListener(DataBroker db, OvsdbConnectionManager cm,
InstanceIdentifierCodec instanceIdentifierCodec) {
this.cm = cm;
this.db = db;
this.instanceIdentifierCodec = instanceIdentifierCodec;
InstanceIdentifier<Node> path = InstanceIdentifier
.create(NetworkTopology.class)
.child(Topology.class, new TopologyKey(SouthboundConstants.OVSDB_TOPOLOGY_ID))
.child(Node.class);
DataTreeIdentifier<Node> dataTreeIdentifier =
new DataTreeIdentifier<>(LogicalDatastoreType.CONFIGURATION, path);
registration = db.registerDataTreeChangeListener(dataTreeIdentifier, this);
LOG.info("OVSDB topology listener has been registered.");
}
@Override
public void close() {
registration.close();
LOG.info("OVSDB topology listener has been closed.");
}
@Override
public void onDataTreeChanged(@Nonnull Collection<DataTreeModification<Node>> changes) {
LOG.trace("onDataTreeChanged: {}", changes);
// Connect first if necessary
connect(changes);
// Update connections if necessary
updateConnections(changes);
// Update the actual data
updateData(changes);
// Disconnect if necessary
disconnect(changes);
LOG.trace("onDataTreeChanged: exit");
}
private void connect(@Nonnull Collection<DataTreeModification<Node>> changes) {
for (DataTreeModification<Node> change : changes) {
if (change.getRootNode().getModificationType() == DataObjectModification.ModificationType.WRITE || change
.getRootNode().getModificationType() == DataObjectModification.ModificationType.SUBTREE_MODIFIED) {
DataObjectModification<OvsdbNodeAugmentation> ovsdbNodeModification =
change.getRootNode().getModifiedAugmentation(OvsdbNodeAugmentation.class);
if (ovsdbNodeModification != null && ovsdbNodeModification.getDataBefore() == null
&& ovsdbNodeModification.getDataAfter() != null
&& ovsdbNodeModification.getDataAfter().getConnectionInfo() != null) {
OvsdbNodeAugmentation ovsdbNode = ovsdbNodeModification.getDataAfter();
ConnectionInfo key = ovsdbNode.getConnectionInfo();
InstanceIdentifier<Node> iid = cm.getInstanceIdentifier(key);
if (iid != null) {
LOG.warn("Connection to device {} already exists. Plugin does not allow multiple connections "
+ "to same device, hence dropping the request {}", key, ovsdbNode);
} else {
try {
InstanceIdentifier<Node> instanceIdentifier = change.getRootPath().getRootIdentifier();
cm.connect(instanceIdentifier, ovsdbNode);
LOG.info("OVSDB node has been connected: {}",ovsdbNode);
} catch (UnknownHostException | ConnectException e) {
LOG.warn("Failed to connect to ovsdbNode", e);
}
}
}
}
}
}
private void disconnect(@Nonnull Collection<DataTreeModification<Node>> changes) {
for (DataTreeModification<Node> change : changes) {
if (change.getRootNode().getModificationType() == DataObjectModification.ModificationType.DELETE) {
DataObjectModification<OvsdbNodeAugmentation> ovsdbNodeModification =
change.getRootNode().getModifiedAugmentation(OvsdbNodeAugmentation.class);
if (ovsdbNodeModification != null && ovsdbNodeModification.getDataBefore() != null) {
OvsdbNodeAugmentation ovsdbNode = ovsdbNodeModification.getDataBefore();
ConnectionInfo key = ovsdbNode.getConnectionInfo();
InstanceIdentifier<Node> iid = cm.getInstanceIdentifier(key);
try {
cm.disconnect(ovsdbNode);
LOG.info("OVSDB node has been disconnected:{}", ovsdbNode);
cm.stopConnectionReconciliationIfActive(iid.firstIdentifierOf(Node.class), ovsdbNode);
} catch (UnknownHostException e) {
LOG.warn("Failed to disconnect ovsdbNode", e);
}
}
}
if (change.getRootNode().getModificationType() == DataObjectModification.ModificationType.WRITE) {
DataObjectModification<OvsdbNodeAugmentation> ovsdbNodeModification =
change.getRootNode().getModifiedAugmentation(OvsdbNodeAugmentation.class);
if (ovsdbNodeModification != null
&& ovsdbNodeModification.getModifiedChildContainer(ConnectionInfo.class) != null) {
DataObjectModification<ConnectionInfo> connectionInfoDOM =
ovsdbNodeModification.getModifiedChildContainer(ConnectionInfo.class);
if (connectionInfoDOM.getModificationType() == DataObjectModification.ModificationType.DELETE
&& connectionInfoDOM.getDataBefore() != null) {
ConnectionInfo key = connectionInfoDOM.getDataBefore();
InstanceIdentifier<Node> iid = cm.getInstanceIdentifier(key);
try {
OvsdbNodeAugmentation ovsdbNode = ovsdbNodeModification.getDataBefore();
cm.disconnect(ovsdbNode);
LOG.warn("OVSDB node {} has been disconnected, because connection-info related to "
+ "the node is removed by user, but node still exist.", ovsdbNode);
cm.stopConnectionReconciliationIfActive(iid.firstIdentifierOf(Node.class), ovsdbNode);
} catch (UnknownHostException e) {
LOG.warn("Failed to disconnect ovsdbNode", e);
}
}
}
}
}
}
private void updateConnections(@Nonnull Collection<DataTreeModification<Node>> changes) {
for (DataTreeModification<Node> change : changes) {
if (change.getRootNode().getModificationType() == DataObjectModification.ModificationType.WRITE || change
.getRootNode().getModificationType() == DataObjectModification.ModificationType.SUBTREE_MODIFIED) {
DataObjectModification<OvsdbNodeAugmentation> ovsdbNodeModification =
change.getRootNode().getModifiedAugmentation(OvsdbNodeAugmentation.class);
if (ovsdbNodeModification != null && ovsdbNodeModification.getDataBefore() != null
&& ovsdbNodeModification.getDataAfter() != null
&& ovsdbNodeModification.getDataAfter().getConnectionInfo() != null) {
OvsdbClient client = cm.getClient(ovsdbNodeModification.getDataAfter().getConnectionInfo());
if (client == null) {
if (ovsdbNodeModification.getDataBefore() != null) {
try {
cm.disconnect(ovsdbNodeModification.getDataBefore());
cm.connect(change.getRootPath().getRootIdentifier(), ovsdbNodeModification
.getDataAfter());
} catch (UnknownHostException | ConnectException e) {
LOG.warn("Error disconnecting from or connecting to ovsdbNode", e);
}
}
}
}
}
}
}
private void updateData(@Nonnull Collection<DataTreeModification<Node>> changes) {
for (Entry<OvsdbConnectionInstance, Collection<DataTreeModification<Node>>> connectionInstanceEntry :
changesPerConnectionInstance(changes).entrySet()) {
OvsdbConnectionInstance connectionInstance = connectionInstanceEntry.getKey();
Collection<DataTreeModification<Node>> clientChanges = connectionInstanceEntry.getValue();
connectionInstance.transact(new TransactCommandAggregator(),
new BridgeOperationalState(db, clientChanges), clientChanges, instanceIdentifierCodec);
}
}
private Map<OvsdbConnectionInstance, Collection<DataTreeModification<Node>>> changesPerConnectionInstance(
@Nonnull Collection<DataTreeModification<Node>> changes) {
Map<OvsdbConnectionInstance, Collection<DataTreeModification<Node>>> result = new HashMap<>();
for (DataTreeModification<Node> change : changes) {
OvsdbConnectionInstance client = null;
Node node = change.getRootNode().getDataAfter() != null
? change.getRootNode().getDataAfter() : change.getRootNode().getDataBefore();
if (node != null) {
InstanceIdentifier<Node> nodeIid;
Augmentation nodeAug = node.getAugmentation(OvsdbNodeAugmentation.class) != null
? node.getAugmentation(OvsdbNodeAugmentation.class)
: node.getAugmentation(OvsdbBridgeAugmentation.class);
if (nodeAug instanceof OvsdbNodeAugmentation) {
OvsdbNodeAugmentation ovsdbNode = (OvsdbNodeAugmentation) nodeAug;
if (ovsdbNode.getConnectionInfo() != null) {
client = cm.getConnectionInstance(ovsdbNode.getConnectionInfo());
} else {
client = cm.getConnectionInstance(SouthboundMapper.createInstanceIdentifier(node.getNodeId()));
}
}
if (nodeAug instanceof OvsdbBridgeAugmentation) {
OvsdbBridgeAugmentation bridgeAugmentation = (OvsdbBridgeAugmentation)nodeAug;
if (bridgeAugmentation.getManagedBy() != null) {
nodeIid = (InstanceIdentifier<Node>) bridgeAugmentation.getManagedBy().getValue();
client = cm.getConnectionInstance(nodeIid);
}
}
if (client == null) {
//Try getting from change root identifier
client = cm.getConnectionInstance(change.getRootPath().getRootIdentifier());
}
} else {
LOG.warn("Following change don't have after/before data {}", change);
}
if (client != null) {
LOG.debug("Found client for {}", node);
/*
* As of now data change sets are processed by single thread, so we can assume that device will
* be connected and ownership will be decided before sending any instructions down to the device.
* Note:Processing order in onDataChange() method should not change. If processing is changed to
* use multiple thread, we might need to take care of corner cases, where ownership is not decided
* but transaction are ready to go to switch. In that scenario, either we need to queue those task
* till ownership is decided for that specific device.
* Given that each DataChangeNotification is notified through separate thread, so we are already
* multi threaded and i don't see any need to further parallelism per DataChange
* notifications processing.
*/
if (cm.getHasDeviceOwnership(client.getMDConnectionInfo())) {
LOG.debug("*This* instance of southbound plugin is an owner of the device {}", node);
result.computeIfAbsent(client, key -> new ArrayList<>()).add(change);
} else {
LOG.debug("*This* instance of southbound plugin is *not* an owner of the device {}", node);
}
} else {
LOG.debug("Did not find client for {}", node);
}
}
return result;
}
}