/*
* Copyright (c) 2016 Cisco Systems, 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.groupbasedpolicy.renderer.ios_xe_provider.impl.manager;
import static org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNodeConnectionStatus.ConnectionStatus.Connected;
import static org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNodeConnectionStatus.ConnectionStatus.Connecting;
import static org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNodeConnectionStatus.ConnectionStatus.UnableToConnect;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.opendaylight.controller.md.sal.binding.api.DataBroker;
import org.opendaylight.controller.md.sal.binding.api.MountPoint;
import org.opendaylight.controller.md.sal.binding.api.MountPointService;
import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction;
import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
import org.opendaylight.controller.sal.binding.api.BindingAwareBroker;
import org.opendaylight.groupbasedpolicy.renderer.ios_xe_provider.impl.writer.NodeWriter;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Host;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv4Address;
import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.renderers.renderer.renderer.nodes.RendererNode;
import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.renderers.renderer.renderer.nodes.RendererNodeBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.renderers.renderer.renderer.nodes.RendererNodeKey;
import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNode;
import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNodeConnectionParameters;
import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNodeConnectionStatus.ConnectionStatus;
import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.status.AvailableCapabilities;
import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.status.available.capabilities.AvailableCapability;
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.NodeId;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.TopologyId;
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.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeKey;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.CheckedFuture;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
public class NodeManager {
private static final TopologyId TOPOLOGY_ID = new TopologyId("topology-netconf");
private static final Logger LOG = LoggerFactory.getLogger(NodeManager.class);
private final DataBroker dataBroker;
private final MountPointService mountService;
private final List<String> requiredCapabilities;
public NodeManager(final DataBroker dataBroker, final BindingAwareBroker.ProviderContext session) {
this.dataBroker = Preconditions.checkNotNull(dataBroker);
mountService = Preconditions.checkNotNull(session.getSALService(MountPointService.class));
requiredCapabilities = new RequiredCapabilities().initializeRequiredCapabilities();
}
public void syncNodes(final Node dataAfter, final Node dataBefore) {
// New node
if (dataBefore == null && dataAfter != null) {
createNode(dataAfter);
}
// Connected/disconnected node
if (dataBefore != null && dataAfter != null) {
updateNode(dataAfter);
}
// Removed node
if (dataBefore != null && dataAfter == null) {
removeNode(dataBefore);
}
}
private void createNode(final Node node) {
LOG.info("Registering new node {}", node.getNodeId().getValue());
final NetconfNode netconfNode = getNodeAugmentation(node);
if (netconfNode == null) {
return;
}
final ConnectionStatus connectionStatus = netconfNode.getConnectionStatus();
switch (connectionStatus) {
case Connecting: {
LOG.info("Connecting device {} ...", node.getNodeId().getValue());
break;
}
case Connected: {
resolveConnectedNode(node, netconfNode);
break;
}
case UnableToConnect: {
LOG.info("Unable to connect device {}", node.getNodeId().getValue());
break;
}
}
}
/**
* Update previously added node. According to actual connection status, appropriate action is performed
*
* @param node to resolve
*/
private void updateNode(final Node node) {
final NetconfNode netconfNode = getNodeAugmentation(node);
if (netconfNode == null || netconfNode.getConnectionStatus() == null) {
LOG.info("Node {} does not contain connection status", node.getNodeId().getValue());
return;
}
final ConnectionStatus afterNodeStatus = netconfNode.getConnectionStatus();
if (afterNodeStatus.equals(Connected)) {
resolveConnectedNode(node, netconfNode);
}
if (afterNodeStatus.equals(Connecting)) {
LOG.info("Node {} has been disconnected, removing from available nodes", node.getNodeId().getValue());
resolveDisconnectedNode(node);
}
if (afterNodeStatus.equals(UnableToConnect)) {
LOG.info("Unable to connect node {}, removing from available nodes", node.getNodeId().getValue());
resolveDisconnectedNode(node);
}
}
/**
* Removes previously added node. This node is also disconnected and removed from available nodes
*
* @param node to remove
*/
private void removeNode(final Node node) {
Futures.addCallback(resolveDisconnectedNode(node), new FutureCallback<Boolean>() {
@Override
public void onSuccess(@Nullable Boolean result) {
if (Boolean.TRUE.equals(result)) {
LOG.info("Node {} has been removed", node.getNodeId().getValue());
} else {
LOG.warn("Failed to remove node {}", node.getNodeId().getValue());
}
}
@Override
public void onFailure(@Nullable Throwable throwable) {
LOG.warn("Exception thrown when removing node... {}", throwable);
}
});
}
/**
* Resolve node with {@link ConnectionStatus#Connected}. This node is reachable and can be added to nodes available
* for renderers
*
* @param node to add to available nodes
* @param netconfNode node's netconf augmentation
*/
private void resolveConnectedNode(final Node node, @Nonnull final NetconfNode netconfNode) {
final InstanceIdentifier mountPointIid = getMountpointIid(node);
// Mountpoint iid == path in renderer-node
final RendererNode rendererNode = remapNode(mountPointIid);
final NodeWriter nodeWriter = new NodeWriter();
nodeWriter.cache(rendererNode);
if (!isCapableNetconfDevice(node, netconfNode)) {
resolveDisconnectedNode(node);
return;
}
final IpAddress managementIpAddress = netconfNode.getHost().getIpAddress();
if (managementIpAddress == null) {
LOG.warn("Node {} does not contain management ip address", node.getNodeId().getValue());
resolveDisconnectedNode(node);
return;
}
Futures.addCallback(nodeWriter.commitToDatastore(dataBroker), new FutureCallback<Boolean>() {
@Override
public void onSuccess(@Nullable Boolean result) {
if (Boolean.TRUE.equals(result)) {
LOG.info("Node {} is ready, added to available nodes for IOS-XE Renderer", node.getNodeId().getValue());
} else {
LOG.warn("Connected node {} has not been resolved", node.getNodeId().getValue());
}
}
@Override
public void onFailure(@Nullable Throwable throwable) {
LOG.warn("Exception thrown when resolving node... {}", throwable);
}
});
}
/**
* Depending on action, this method is called when node is not reachable anymore. Such a node is removed from nodes
* available for renderers. Reasons why the node is offline can vary, therefore logging should be handled outside
*
* @param node to remove from available nodes
* @return true if removed, false otherwise
*/
private ListenableFuture<Boolean> resolveDisconnectedNode(final Node node) {
final InstanceIdentifier mountPointIid = getMountpointIid(node);
final RendererNode rendererNode = remapNode(mountPointIid);
final NodeWriter nodeWriter = new NodeWriter();
nodeWriter.cache(rendererNode);
return nodeWriter.removeFromDatastore(dataBroker);
}
/**
* Node is remapped as renderer node with instance identifier. Used when reporting status for renderer manager
*
* @param path node IID
* @return {@link RendererNode} object with path
*/
private RendererNode remapNode(final InstanceIdentifier path) {
final RendererNodeBuilder rendererNodeBuilder = new RendererNodeBuilder();
rendererNodeBuilder.setKey(new RendererNodeKey(path))
.setNodePath(path);
return rendererNodeBuilder.build();
}
private InstanceIdentifier getMountpointIid(final Node node) {
return InstanceIdentifier.builder(NetworkTopology.class)
.child(Topology.class, new TopologyKey(TOPOLOGY_ID))
.child(Node.class, new NodeKey(node.getNodeId())).build();
}
private boolean isCapableNetconfDevice(final Node node, @Nonnull final NetconfNode netconfAugmentation) {
final AvailableCapabilities available = netconfAugmentation.getAvailableCapabilities();
if (available == null || available.getAvailableCapability() == null || available.getAvailableCapability().isEmpty()) {
LOG.warn("Node {} does not contain any capabilities", node.getNodeId().getValue());
return false;
}
if (!capabilityCheck(netconfAugmentation.getAvailableCapabilities().getAvailableCapability())) {
LOG.warn("Node {} does not contain all capabilities required by io-xe-renderer",
node.getNodeId().getValue());
return false;
}
return true;
}
private boolean capabilityCheck(final List<AvailableCapability> capabilities) {
final List<String> availableCapabilities = capabilities.stream()
.map(AvailableCapability::getCapability)
.collect(Collectors.toList());
return requiredCapabilities.stream()
.allMatch(availableCapabilities::contains);
}
@Nullable
DataBroker getNodeMountPoint(final InstanceIdentifier mountPointIid) {
if (mountPointIid == null) {
return null;
}
final MountPoint mountPoint = ((Function<InstanceIdentifier, MountPoint>) instanceIdentifier -> {
Optional<MountPoint> optionalObject = mountService.getMountPoint(mountPointIid);
if (optionalObject.isPresent()) {
return optionalObject.get();
}
LOG.debug("Cannot obtain mountpoint with IID {}", mountPointIid);
return null;
}).apply(mountPointIid);
if (mountPoint == null) {
return null;
}
return ((Function<MountPoint, DataBroker>) mountPointParam -> {
Optional<DataBroker> optionalDataBroker = mountPointParam.getService(DataBroker.class);
if (optionalDataBroker.isPresent()) {
return optionalDataBroker.get();
}
LOG.debug("Cannot obtain data broker from mountpoint {}", mountPointParam);
return null;
}).apply(mountPoint);
}
NodeId getNodeIdByMountpointIid(final InstanceIdentifier mountpointIid) {
final NodeKey identifier = (NodeKey) mountpointIid.firstKeyOf(Node.class);
return identifier.getNodeId();
}
java.util.Optional<String> getNodeManagementIpByMountPointIid(final InstanceIdentifier<?> mountpointIid) {
final NodeId nodeId = getNodeIdByMountpointIid(mountpointIid);
final InstanceIdentifier<Node> nodeIid = InstanceIdentifier.builder(NetworkTopology.class)
.child(Topology.class, new TopologyKey(new TopologyId(NodeManager.TOPOLOGY_ID)))
.child(Node.class, new NodeKey(nodeId))
.build();
final ReadOnlyTransaction rTx = dataBroker.newReadOnlyTransaction();
final CheckedFuture<Optional<Node>, ReadFailedException> submitFuture =
rTx.read(LogicalDatastoreType.CONFIGURATION, nodeIid);
rTx.close();
try {
Optional<Node> nodeOptional = submitFuture.checkedGet();
if (nodeOptional.isPresent()) {
final NetconfNode netconfNode = getNodeAugmentation(nodeOptional.get());
return java.util.Optional.ofNullable(netconfNode)
.map(NetconfNodeConnectionParameters::getHost)
.map(Host::getIpAddress)
.map(IpAddress::getIpv4Address)
.map(Ipv4Address::getValue);
}
} catch (ReadFailedException e) {
LOG.warn("Read node failed {}", nodeId, e);
}
return java.util.Optional.empty();
}
private NetconfNode getNodeAugmentation(final Node node) {
final NetconfNode netconfNode = node.getAugmentation(NetconfNode.class);
if (netconfNode == null) {
LOG.warn("Node {} is not a netconf device", node.getNodeId().getValue());
return null;
}
return netconfNode;
}
private static class RequiredCapabilities {
private static final String NED ="(urn:ios?revision=2016-03-08)ned";
private static final String TAILF_COMMON = "(http://tail-f.com/yang/common?revision=2015-05-22)tailf-common";
private static final String TAILF_CLI_EXTENSION = "(http://tail-f.com/yang/common?revision=2015-03-19)tailf-cli-extensions";
private static final String TAILF_META_EXTENSION = "(http://tail-f.com/yang/common?revision=2013-11-07)tailf-meta-extensions";
private static final String IETF_YANG_TYPES = "(urn:ietf:params:xml:ns:yang:ietf-yang-types?revision=2013-07-15)ietf-yang-types";
private static final String IETF_INET_TYPES = "(urn:ietf:params:xml:ns:yang:ietf-inet-types?revision=2013-07-15)ietf-inet-types";
/**
* Initialize all common capabilities required by IOS-XE renderer. Any connected node is examined whether it's
* an appropriate device to handle configuration created by this renderer. A device has to support all capabilities
* in list below.
*
* @return list of string representations of required capabilities
*/
List<String> initializeRequiredCapabilities() {
final String capabilityEntries[] = {NED, TAILF_COMMON, TAILF_CLI_EXTENSION, TAILF_META_EXTENSION,
IETF_YANG_TYPES, IETF_INET_TYPES};
return Arrays.asList(capabilityEntries);
}
}
}