All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opendaylight.l2switch.hosttracker.plugin.internal.HostTrackerImpl Maven / Gradle / Ivy

There is a newer version: 0.7.4
Show newest version
/**
 * Copyright (c) 2014 André Martins, Colin Dixon, Evan Zeller 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.l2switch.hosttracker.plugin.internal;

import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nonnull;
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.binding.api.ReadOnlyTransaction;
import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
import org.opendaylight.l2switch.hosttracker.plugin.inventory.Host;
import org.opendaylight.l2switch.hosttracker.plugin.util.Utilities;
import org.opendaylight.yang.gen.v1.urn.opendaylight.address.tracker.rev140617.AddressCapableNodeConnector;
import org.opendaylight.yang.gen.v1.urn.opendaylight.address.tracker.rev140617.address.node.connector.Addresses;
import org.opendaylight.yang.gen.v1.urn.opendaylight.host.tracker.rev140624.HostId;
import org.opendaylight.yang.gen.v1.urn.opendaylight.host.tracker.rev140624.HostNode;
import org.opendaylight.yang.gen.v1.urn.opendaylight.host.tracker.rev140624.host.AttachmentPointsBuilder;
import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes;
import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnector;
import org.opendaylight.yang.gen.v1.urn.opendaylight.l2switch.host.tracker.config.rev140528.HostTrackerConfig;
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.TopologyId;
import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.TpId;
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.Link;
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.DataObject;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@SuppressWarnings("rawtypes")
public class HostTrackerImpl implements DataTreeChangeListener {

    private static final int CPUS = Runtime.getRuntime().availableProcessors();

    private static final String TOPOLOGY_NAME = "flow:1";

    private static final Logger LOG = LoggerFactory.getLogger(HostTrackerImpl.class);

    private final DataBroker dataService;
    private final String topologyId;
    private final long hostPurgeInterval;
    private final long hostPurgeAge;

    private final ScheduledExecutorService exec = Executors.newScheduledThreadPool(CPUS);

    private final ConcurrentClusterAwareHostHashMap hosts;
    private final ConcurrentClusterAwareLinkHashMap links;
    private final OperationProcessor opProcessor;
    private final Thread processorThread;
    private ListenerRegistration addrsNodeListenerRegistration;
    private ListenerRegistration hostNodeListenerRegistration;
    private ListenerRegistration linkNodeListenerRegistration;

    /**
     * It creates hosts using reference to MD-SAl / toplogy module. For every hostPurgeIntervalInput time interval
     * it requests to purge hosts that are not seen for hostPurgeAgeInput time interval.
     *
     * @param dataService A reference to the MD-SAL
     * @param config Default configuration
     */
    public HostTrackerImpl(final DataBroker dataService, final HostTrackerConfig config) {
        Preconditions.checkNotNull(dataService, "dataBrokerService should not be null.");
        Preconditions.checkArgument(config.getHostPurgeAge() >= 0, "hostPurgeAgeInput must be non-negative");
        Preconditions.checkArgument(config.getHostPurgeInterval() >= 0, "hostPurgeIntervalInput must be non-negative");
        this.dataService = dataService;
        this.hostPurgeAge = config.getHostPurgeAge();
        this.hostPurgeInterval = config.getHostPurgeInterval();
        this.opProcessor = new OperationProcessor(dataService);
        processorThread = new Thread(opProcessor);
        final String maybeTopologyId = config.getTopologyId();
        if (maybeTopologyId == null || maybeTopologyId.isEmpty()) {
            this.topologyId = TOPOLOGY_NAME;
        } else {
            this.topologyId = maybeTopologyId;
        }
        this.hosts = new ConcurrentClusterAwareHostHashMap(opProcessor, this.topologyId);
        this.links = new ConcurrentClusterAwareLinkHashMap(opProcessor);

        if (hostPurgeInterval > 0) {
            exec.scheduleWithFixedDelay(() -> purgeHostsNotSeenInLast(hostPurgeAge), 0, hostPurgeInterval,
                    TimeUnit.SECONDS);
        }
    }

    @SuppressWarnings("unchecked")
    public void init() {
        processorThread.start();

        InstanceIdentifier addrCapableNodeConnectors = //
                InstanceIdentifier.builder(Nodes.class) //
                        .child(org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node.class)
                        .child(NodeConnector.class) //
                        .augmentation(AddressCapableNodeConnector.class)//
                        .child(Addresses.class).build();
        this.addrsNodeListenerRegistration = dataService.registerDataTreeChangeListener(new DataTreeIdentifier<>(
                LogicalDatastoreType.OPERATIONAL, addrCapableNodeConnectors), (DataTreeChangeListener)this);

        InstanceIdentifier hostNodes = InstanceIdentifier.builder(NetworkTopology.class)//
                .child(Topology.class, new TopologyKey(new TopologyId(topologyId)))//
                .child(Node.class)
                .augmentation(HostNode.class).build();
        this.hostNodeListenerRegistration = dataService.registerDataTreeChangeListener(new DataTreeIdentifier<>(
                LogicalDatastoreType.OPERATIONAL, hostNodes), (DataTreeChangeListener)this);

        InstanceIdentifier linkIID = InstanceIdentifier.builder(NetworkTopology.class)//
                .child(Topology.class, new TopologyKey(new TopologyId(topologyId)))
                .child(Link.class).build();

        this.linkNodeListenerRegistration = dataService.registerDataTreeChangeListener(new DataTreeIdentifier<>(
                LogicalDatastoreType.OPERATIONAL, linkIID), (DataTreeChangeListener)this);

        //Processing addresses that existed before we register as a data change listener.
//        ReadOnlyTransaction newReadOnlyTransaction = dataService.newReadOnlyTransaction();
//        InstanceIdentifier iinc = addrCapableNodeConnectors.firstIdentifierOf(NodeConnector.class);
//        InstanceIdentifier iin
//                = addrCapableNodeConnectors.firstIdentifierOf(
//                    org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node.class);
//        ListenableFuture> dataFuture = newReadOnlyTransaction.read(
//            LogicalDatastoreType.OPERATIONAL, iinc);
//        try {
//            NodeConnector get = dataFuture.get().get();
//            log.trace("test "+get);
//        } catch (InterruptedException | ExecutionException ex) {
//            java.util.logging.Logger.getLogger(HostTrackerImpl.class.getName()).log(Level.SEVERE, null, ex);
//        }
//        Futures.addCallback(dataFuture, new FutureCallback>() {
//            @Override
//            public void onSuccess(final Optional result) {
//                if (result.isPresent()) {
//                    log.trace("Processing NEW NODE? " + result.get().getId().getValue());
////                    processHost(result, dataObject, node);
//                }
//            }
//
//            @Override
//            public void onFailure(Throwable arg0) {
//            }
//        });
    }

    @Override
    public void onDataTreeChanged(Collection> changes) {
        exec.submit(() -> {
            for (DataTreeModification change: changes) {
                DataObjectModification rootNode = change.getRootNode();
                final InstanceIdentifier identifier = change.getRootPath().getRootIdentifier();
                switch (rootNode.getModificationType()) {
                    case SUBTREE_MODIFIED:
                    case WRITE:
                        onModifiedData(identifier, rootNode);
                        break;
                    case DELETE:
                        onDeletedData(identifier, rootNode);
                        break;
                    default:
                        break;
                }
            }
        });
    }

    @SuppressWarnings("unchecked")
    private void onModifiedData(InstanceIdentifier iid, DataObjectModification rootNode) {
        final DataObject dataObject = rootNode.getDataAfter();
        if (dataObject instanceof Addresses) {
            packetReceived((Addresses) dataObject, iid);
        } else if (dataObject instanceof Node) {
            hosts.putLocally((InstanceIdentifier) iid, Host.createHost((Node) dataObject));
        } else if (dataObject instanceof  Link) {
            links.putLocally((InstanceIdentifier) iid, (Link) dataObject);
        }
    }

    @SuppressWarnings("unchecked")
    private void onDeletedData(InstanceIdentifier iid, DataObjectModification rootNode) {
        if (iid.getTargetType().equals(Node.class)) {
            Node node = (Node) rootNode.getDataBefore();
            InstanceIdentifier iiN = (InstanceIdentifier) iid;
            HostNode hostNode = node.getAugmentation(HostNode.class);
            if (hostNode != null) {
                hosts.removeLocally(iiN);
            }
        } else if (iid.getTargetType().equals(Link.class)) {
            // TODO performance improvement here
            InstanceIdentifier iiL = (InstanceIdentifier) iid;
            links.removeLocally(iiL);
            linkRemoved((InstanceIdentifier) iid, (Link) rootNode.getDataBefore());
        }

    }

    public void packetReceived(Addresses addrs, InstanceIdentifier ii) {
        InstanceIdentifier iinc = ii.firstIdentifierOf(NodeConnector.class);
        InstanceIdentifier iin =
            ii.firstIdentifierOf(org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node.class);

        ListenableFuture> futureNodeConnector;
        ListenableFuture> futureNode;
        try (ReadOnlyTransaction readTx = dataService.newReadOnlyTransaction()) {
            futureNodeConnector = readTx.read(LogicalDatastoreType.OPERATIONAL, iinc);
            futureNode = readTx.read(LogicalDatastoreType.OPERATIONAL, iin);
            readTx.close();
        }
        Optional opNodeConnector = null;
        Optional opNode = null;
        try {
            opNodeConnector = futureNodeConnector.get();
            opNode = futureNode.get();
        } catch (ExecutionException | InterruptedException ex) {
            LOG.warn(ex.getLocalizedMessage());
        }
        if (opNode != null && opNode.isPresent()
                && opNodeConnector != null && opNodeConnector.isPresent()) {
            processHost(opNode.get(), opNodeConnector.get(), addrs);
        }
    }

    private void processHost(org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node node,
                             NodeConnector nodeConnector,
                             Addresses addrs) {
        List hostsToMod = new ArrayList<>();
        List hostsToRem = new ArrayList<>();
        List linksToRem = new ArrayList<>();
        List linksToAdd = new ArrayList<>();
        synchronized (hosts) {
            LOG.trace("Processing nodeConnector: {} ", nodeConnector.getId().toString());
            HostId hostId = Host.createHostId(addrs);
            if (hostId != null) {
                if (isNodeConnectorInternal(nodeConnector)) {
                    LOG.trace("NodeConnector is internal: {} ", nodeConnector.getId().toString());

                    removeNodeConnectorFromHost(hostsToMod, hostsToRem, nodeConnector);
                    hosts.removeAll(hostsToRem);
                    hosts.putAll(hostsToMod);
                } else {
                    LOG.trace("NodeConnector is NOT internal {} ", nodeConnector.getId().toString());
                    Host host = new Host(addrs, nodeConnector);
                    if (hosts.containsKey(host.getId())) {
                        hosts.get(host.getId()).mergeHostWith(host);
                    } else {
                        hosts.put(host.getId(), host);
                    }
                    List newLinks = hosts.get(host.getId()).createLinks(node);
                    if (newLinks != null) {
                        linksToAdd.addAll(newLinks);
                    }
                    hosts.submit(host.getId());
                }
            }
        }
        writeDataToDataStore(linksToAdd, linksToRem);
    }

    /**
     * It verifies if a given NodeConnector is *internal*. An *internal*
     * NodeConnector is considered to be all NodeConnetors that are NOT attached
     * to hosts created by hosttracker.
     *
     * @param nodeConnector the nodeConnector to check if it is internal or not.
     * @return true if it was found a host connected to this nodeConnetor, false
     *     if it was not found a network topology or it was not found a host connected to this nodeConnetor.
     */
    private boolean isNodeConnectorInternal(NodeConnector nodeConnector) {
        TpId tpId = new TpId(nodeConnector.getKey().getId().getValue());
        InstanceIdentifier ntII
                = InstanceIdentifier.builder(NetworkTopology.class).build();
        ListenableFuture> lfONT;
        try (ReadOnlyTransaction rot = dataService.newReadOnlyTransaction()) {
            lfONT = rot.read(LogicalDatastoreType.OPERATIONAL, ntII);
        }
        Optional optionalNT;
        try {
            optionalNT = lfONT.get();
        } catch (InterruptedException | ExecutionException ex) {
            LOG.warn(ex.getLocalizedMessage());
            return false;
        }
        if (optionalNT.isPresent()) {
            NetworkTopology networkTopo = optionalNT.get();
            for (Topology t : networkTopo.getTopology()) {
                if (t.getLink() != null) {
                    for (Link l : t.getLink()) {
                        if (l.getSource().getSourceTp().equals(tpId)
                                && !l.getDestination().getDestTp().getValue().startsWith(Host.NODE_PREFIX)
                                || l.getDestination().getDestTp().equals(tpId)
                                && !l.getSource().getSourceTp().getValue().startsWith(Host.NODE_PREFIX)) {
                            return true;
                        }
                    }
                }
            }
        }
        return false;
    }

    private void removeLinksFromHosts(List hostsToMod, List hostsToRem, Link linkRemoved) {
        for (Host h : hosts.values()) {
            h.removeTerminationPoint(linkRemoved.getSource().getSourceTp());
            h.removeTerminationPoint(linkRemoved.getDestination().getDestTp());
            if (h.isOrphan()) {
                hostsToRem.add(h);
            } else {
                hostsToMod.add(h);
            }
        }
    }

    private void removeNodeConnectorFromHost(List hostsToMod, List hostsToRem, NodeConnector nc) {
        AttachmentPointsBuilder atStD = Utilities.createAPsfromNodeConnector(nc);
        for (Host h : hosts.values()) {
            h.removeAttachmentPoints(atStD);
            if (h.isOrphan()) {
                hostsToRem.add(h);
            } else {
                hostsToMod.add(h);
            }
        }
    }

    private void linkRemoved(InstanceIdentifier iiLink, Link linkRemoved) {
        LOG.trace("linkRemoved");
        List hostsToMod = new ArrayList<>();
        List hostsToRem = new ArrayList<>();
        synchronized (hosts) {
            removeLinksFromHosts(hostsToMod, hostsToRem, linkRemoved);
            hosts.removeAll(hostsToRem);
            hosts.putAll(hostsToMod);
        }
    }

    private void writeDataToDataStore(List linksToAdd, List linksToRemove) {
        if (linksToAdd != null) {
            for (final Link l : linksToAdd) {
                final InstanceIdentifier lIID = Utilities.buildLinkIID(l.getKey(), topologyId);
                LOG.trace("Writing link from MD_SAL: {}", lIID.toString());
                opProcessor.enqueueOperation(tx -> tx.merge(LogicalDatastoreType.OPERATIONAL, lIID, l, true));
            }
        }
        if (linksToRemove != null) {
            for (Link l : linksToRemove) {
                final InstanceIdentifier lIID = Utilities.buildLinkIID(l.getKey(), topologyId);
                LOG.trace("Removing link from MD_SAL: {}", lIID.toString());
                opProcessor.enqueueOperation(tx -> tx.delete(LogicalDatastoreType.OPERATIONAL,  lIID));
            }
        }
    }

    /**
     * Remove all hosts that haven't been observed more recently than the specified number of
     * hostsPurgeAgeInSeconds.
     *
     * @param hostsPurgeAgeInSeconds remove hosts that haven't been observed in longer than this number of
     *               hostsPurgeAgeInSeconds.
     */
    protected void purgeHostsNotSeenInLast(final long hostsPurgeAgeInSeconds) {
        int numHostsPurged = 0;
        final long nowInMillis = System.currentTimeMillis();
        final long nowInSeconds = TimeUnit.MILLISECONDS.toSeconds(nowInMillis);
        // iterate through all hosts in the local cache
        for (Host h : hosts.values()) {
            final HostNode hn = h.getHostNode().getAugmentation(HostNode.class);
            if (hn == null) {
                LOG.warn("Encountered non-host node {} in hosts during purge", h);
            } else if (hn.getAddresses() != null) {
                boolean purgeHosts = false;
                // if the node is a host and has addresses, check to see if it's been seen recently
                purgeHosts = hostReadyForPurge(hn, nowInSeconds,hostsPurgeAgeInSeconds);
                if (purgeHosts) {
                    numHostsPurged = removeHosts(h, numHostsPurged);
                }
            } else {
                LOG.warn("Encountered host node {} with no address in hosts during purge", hn);
            }
        }
        LOG.debug("Number of purged hosts during current purge interval - {}. ", numHostsPurged);
    }

    /**
     * Checks if hosts need to be purged.
     *
     * @param hostNode reference to HostNode class
     * @param currentTimeInSeconds current time in seconds
     * @param expirationPeriod timelimit set to hosts for expiration
     * @return boolean - whether the hosts are ready to be purged
     */
    private boolean hostReadyForPurge(final HostNode hostNode, final long currentTimeInSeconds,
            final long expirationPeriod) {
        // checks if hosts need to be purged
        for (Addresses addrs : hostNode.getAddresses()) {
            long lastSeenTimeInSeconds = addrs.getLastSeen() / 1000;
            if (lastSeenTimeInSeconds > currentTimeInSeconds - expirationPeriod) {
                LOG.debug("Host node {} NOT ready for purge", hostNode);
                return false;
            }
        }
        LOG.debug("Host node {} ready for purge", hostNode);
        return true;
    }

    /**
     * Removes hosts from locally and MD-SAL. Throws warning message if not removed successfully
     *
     * @param host  reference to Host node
     */
    private int removeHosts(@Nonnull final Host host, int numHostsPurged) {
        // remove associated links with the host before removing hosts
        removeAssociatedLinksFromHosts(host);
        // purge hosts from local & MD-SAL database
        if (hosts.remove(host.getId()) != null) {
            numHostsPurged++;
            LOG.debug("Removed host with id {} during purge.", host.getId());
        } else {
            LOG.warn("Unexpected error encountered - Failed to remove host {} during purge", host);
        }

        return numHostsPurged;
    }

    /**
     * Removes links associated with the given hosts from local and MD-SAL database.
     * Throws warning message if not removed successfully.
     *
     * @param host  reference to Host node
     */
    private void removeAssociatedLinksFromHosts(@Nonnull final Host host) {
        if (host.getId() != null) {
            List linksToRemove = new ArrayList<>();
            for (Link link: links.values()) {
                if (link.toString().contains(host.getId().getValue())) {
                    linksToRemove.add(link);
                }
            }
            links.removeAll(linksToRemove);
        } else {
            LOG.warn("Encountered host with no id , Unexpected host id {}. ", host);
        }
    }

    public void close() {
        processorThread.interrupt();
        this.addrsNodeListenerRegistration.close();
        this.hostNodeListenerRegistration.close();
        this.linkNodeListenerRegistration.close();
        this.exec.shutdownNow();
        this.hosts.clear();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy