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

org.opendaylight.protocol.bmp.impl.app.BmpRouterImpl Maven / Gradle / Ivy

There is a newer version: 0.22.6
Show newest version
/*
 * Copyright (c) 2015 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.protocol.bmp.impl.app;

import static java.util.Objects.requireNonNull;

import com.google.common.base.Preconditions;
import com.google.common.net.InetAddresses;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import org.checkerframework.checker.lock.qual.GuardedBy;
import org.checkerframework.checker.lock.qual.Holding;
import org.opendaylight.mdsal.common.api.CommitInfo;
import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
import org.opendaylight.mdsal.dom.api.DOMDataBroker;
import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction;
import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
import org.opendaylight.protocol.bgp.rib.spi.RIBExtensionConsumerContext;
import org.opendaylight.protocol.bmp.api.BmpSession;
import org.opendaylight.protocol.bmp.impl.spi.BmpRouter;
import org.opendaylight.protocol.bmp.impl.spi.BmpRouterPeer;
import org.opendaylight.protocol.util.Ipv4Util;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev200120.OpenMessage;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.PeerId;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bmp.message.rev200120.InitiationMessage;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bmp.message.rev200120.PeerDownNotification;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bmp.message.rev200120.PeerHeader;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bmp.message.rev200120.PeerUpNotification;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bmp.message.rev200120.string.informations.StringInformation;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bmp.monitor.rev200120.RouterId;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bmp.monitor.rev200120.peers.Peer;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bmp.monitor.rev200120.routers.Router;
import org.opendaylight.yangtools.binding.Notification;
import org.opendaylight.yangtools.binding.data.codec.api.BindingCodecTree;
import org.opendaylight.yangtools.yang.common.Empty;
import org.opendaylight.yangtools.yang.common.QName;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
import org.opendaylight.yangtools.yang.data.spi.node.ImmutableNodes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public final class BmpRouterImpl implements BmpRouter, FutureCallback {
    private static final Logger LOG = LoggerFactory.getLogger(BmpRouterImpl.class);

    private static final QName ROUTER_ID_QNAME = QName.create(Router.QNAME, "router-id").intern();
    private static final QName ROUTER_STATUS_QNAME = QName.create(Router.QNAME, "status").intern();
    private static final QName ROUTER_NAME_QNAME = QName.create(Router.QNAME, "name").intern();
    private static final QName ROUTER_DESCRIPTION_QNAME = QName.create(Router.QNAME, "description").intern();
    private static final QName ROUTER_INFO_QNAME = QName.create(Router.QNAME, "info").intern();
    private static final String UP = "up";
    private static final String DOWN = "down";

    private final RouterSessionManager sessionManager;
    @GuardedBy("this")
    private final Map peers = new HashMap<>();
    private final DOMTransactionChain domTxChain;
    private final DOMDataBroker domDataBroker;
    private final RIBExtensionConsumerContext extensions;
    private final BindingCodecTree tree;
    private BmpSession session;
    private RouterId routerId;
    private String routerIp;
    @GuardedBy("this")
    private YangInstanceIdentifier routerYangIId;
    @GuardedBy("this")
    private YangInstanceIdentifier peersYangIId;

    public BmpRouterImpl(final RouterSessionManager sessionManager) {
        this.sessionManager = requireNonNull(sessionManager);
        domDataBroker = sessionManager.getDomDataBroker();
        domTxChain = domDataBroker.createMergingTransactionChain();
        domTxChain.addCallback(this);
        extensions = sessionManager.getExtensions();
        tree = sessionManager.getCodecTree();
    }

    @Override
    public synchronized void onSessionUp(final BmpSession psession) {
        session = psession;
        routerIp = InetAddresses.toAddrString(session.getRemoteAddress());
        routerId = new RouterId(Ipv4Util.getIpAddress(session.getRemoteAddress()));
        // check if this session is redundant
        if (!sessionManager.addSessionListener(this)) {
            LOG.warn("Redundant BMP session with remote router {} ({}) detected. This BMP session will be abandoned.",
                routerIp, session);
            close();
        } else {
            routerYangIId = YangInstanceIdentifier.builder(sessionManager.getRoutersYangIId())
                .nodeWithKey(Router.QNAME, ROUTER_ID_QNAME, routerIp).build();
            peersYangIId = YangInstanceIdentifier.builder(routerYangIId).node(Peer.QNAME).build();
            createRouterEntry();
            LOG.info("BMP session with remote router {} ({}) is up now.", routerIp, session);
        }
    }

    @Override
    public synchronized void onSessionDown(final Exception exception) {
        // we want to tear down as we want to do clean up like closing the transaction chain, etc.
        // even when datastore is not writable (routerYangIId == null / redundant session)
        tearDown();
    }

    @Override
    public void onMessage(final Notification message) {
        if (message instanceof InitiationMessage) {
            onInitiate((InitiationMessage) message);
        } else if (message instanceof PeerUpNotification) {
            onPeerUp((PeerUpNotification) message);
        } else if (message instanceof PeerHeader) {
            delegateToPeer(message);
        }
    }

    @Override
    public synchronized RouterId getRouterId() {
        return routerId;
    }

    @Override
    @SuppressWarnings("checkstyle:IllegalCatch")
    public synchronized void close() {
        if (session != null) {
            try {
                session.close();
            } catch (final Exception exc) {
                LOG.error("Fail to close session.", exc);
            }
        }
    }

    @Holding("this")
    @SuppressWarnings("checkstyle:IllegalCatch")
    private synchronized void tearDown() {
        // the session has been teared down before
        if (session == null) {
            return;
        }
        // we want to display remote router's IP here, as sometimes this.session.close() is already
        // invoked before tearDown(), and session channel is null in this case, which leads to unuseful
        // log information
        LOG.info("BMP Session with remote router {} ({}) went down.", routerIp, session);
        session = null;
        final Iterator it = peers.values().iterator();
        try {
            while (it.hasNext()) {
                it.next().close();
                it.remove();
            }
            domTxChain.close();
        } catch (final Exception e) {
            LOG.error("Failed to properly close BMP application.", e);
        } finally {
            // remove session only when session is valid, otherwise
            // we would remove the original valid session when a redundant connection happens
            // as the routerId is the same for both connection
            if (isDatastoreWritable()) {
                try {
                    // it means the session was closed before it was written to datastore
                    final DOMDataTreeWriteTransaction wTx = domDataBroker.newWriteOnlyTransaction();
                    wTx.delete(LogicalDatastoreType.OPERATIONAL, routerYangIId);
                    wTx.commit().get();
                } catch (final InterruptedException | ExecutionException e) {
                    LOG.error("Failed to remove BMP router data from DS.", e);
                }
                sessionManager.removeSessionListener(this);
            }
        }
    }

    @Override
    public synchronized void onFailure(final Throwable cause) {
        LOG.error("Transaction chain failed.", cause);
    }

    @Override
    public void onSuccess(final Empty value) {
        LOG.debug("Transaction chain finished successfully.");
    }

    private synchronized boolean isDatastoreWritable() {
        return routerYangIId != null;
    }

    private synchronized void createRouterEntry() {
        Preconditions.checkState(isDatastoreWritable());
        final DOMDataTreeWriteTransaction wTx = domTxChain.newWriteOnlyTransaction();
        wTx.put(LogicalDatastoreType.OPERATIONAL, routerYangIId, ImmutableNodes.newMapEntryBuilder()
            .withNodeIdentifier(NodeIdentifierWithPredicates.of(Router.QNAME, ROUTER_ID_QNAME, routerIp))
            .withChild(ImmutableNodes.leafNode(ROUTER_ID_QNAME, routerIp))
            .withChild(ImmutableNodes.leafNode(ROUTER_STATUS_QNAME, DOWN))
            .withChild(ImmutableNodes.newSystemMapBuilder().withNodeIdentifier(new NodeIdentifier(Peer.QNAME)).build())
            .build());
        wTx.commit().addCallback(new FutureCallback() {
            @Override
            public void onSuccess(final CommitInfo result) {
                LOG.trace("Successful commit");
            }

            @Override
            public void onFailure(final Throwable trw) {
                LOG.error("Failed commit", trw);
            }
        }, MoreExecutors.directExecutor());
    }

    private synchronized void onInitiate(final InitiationMessage initiation) {
        Preconditions.checkState(isDatastoreWritable());
        final DOMDataTreeWriteTransaction wTx = domTxChain.newWriteOnlyTransaction();
        wTx.merge(LogicalDatastoreType.OPERATIONAL, routerYangIId, ImmutableNodes.newMapEntryBuilder()
            .withNodeIdentifier(NodeIdentifierWithPredicates.of(Router.QNAME, ROUTER_ID_QNAME, routerIp))
            .withChild(ImmutableNodes.leafNode(ROUTER_NAME_QNAME, initiation.getTlvs().getNameTlv().getName()))
            .withChild(ImmutableNodes.leafNode(ROUTER_DESCRIPTION_QNAME,
                initiation.getTlvs().getDescriptionTlv().getDescription()))
            .withChild(ImmutableNodes.leafNode(ROUTER_INFO_QNAME,
                getStringInfo(initiation.getTlvs().getStringInformation())))
            .withChild(ImmutableNodes.leafNode(ROUTER_STATUS_QNAME, UP))
            .build());
        wTx.commit().addCallback(new FutureCallback() {
            @Override
            public void onSuccess(final CommitInfo result) {
                LOG.trace("Successful commit");
            }

            @Override
            public void onFailure(final Throwable trw) {
                LOG.error("Failed commit", trw);
            }
        }, MoreExecutors.directExecutor());
    }

    private synchronized void onPeerUp(final PeerUpNotification peerUp) {
        final PeerId peerId = getPeerIdFromOpen(peerUp.getReceivedOpen());
        if (!getPeer(peerId).isPresent()) {
            final BmpRouterPeer peer = BmpRouterPeerImpl.createRouterPeer(domTxChain, peersYangIId, peerUp,
                extensions, tree, peerId);
            peers.put(peerId, peer);
            LOG.debug("Router {}: Peer {} goes up.", routerIp, peerId.getValue());
        } else {
            LOG.debug("Peer: {} for Router: {} already exists.", peerId.getValue(), routerIp);
        }
    }

    private synchronized void delegateToPeer(final Notification perPeerMessage) {
        final PeerId peerId = getPeerId((PeerHeader) perPeerMessage);
        final Optional maybePeer = getPeer(peerId);
        if (maybePeer.isPresent()) {
            maybePeer.orElseThrow().onPeerMessage(perPeerMessage);
            if (perPeerMessage instanceof PeerDownNotification) {
                peers.remove(peerId);
                LOG.debug("Router {}: Peer {} removed.", routerIp, peerId.getValue());
            }
        } else {
            LOG.debug("Peer: {} for Router: {} was not found.", peerId.getValue(), routerIp);
        }
    }

    private Optional getPeer(final PeerId peerId) {
        return Optional.ofNullable(peers.get(peerId));
    }

    private static PeerId getPeerId(final PeerHeader peerHeader) {
        return new PeerId(peerHeader.getPeerHeader().getBgpId().getValue());
    }

    private static PeerId getPeerIdFromOpen(final OpenMessage open) {
        return new PeerId(open.getBgpIdentifier().getValue());
    }

    private static String getStringInfo(final List info) {
        final StringBuilder builder = new StringBuilder();
        if (info != null) {
            for (final StringInformation string : info) {
                if (string.getStringTlv() != null) {
                    builder.append(string.getStringTlv().getStringInfo());
                    builder.append(";");
                }
            }
        }
        return builder.toString();
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy