BUG-4714 : No routes from loc-rib are advertised
[bgpcep.git] / bgp / rib-impl / src / main / java / org / opendaylight / protocol / bgp / rib / impl / LocRibWriter.java
index 8d958cf341fbc9d841dd092ba4c46ec590186af9..981307e1acce4119c4a698145d67d167696ea904 100644 (file)
@@ -7,10 +7,10 @@
  */
 package org.opendaylight.protocol.bgp.rib.impl;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import com.google.common.primitives.UnsignedInteger;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
@@ -23,7 +23,6 @@ import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeChangeService;
 import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeIdentifier;
 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
 import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
-import org.opendaylight.protocol.bgp.rib.impl.spi.RIBSupportContext;
 import org.opendaylight.protocol.bgp.rib.impl.spi.RIBSupportContextRegistry;
 import org.opendaylight.protocol.bgp.rib.spi.RIBSupport;
 import org.opendaylight.protocol.bgp.rib.spi.RibSupportUtils;
@@ -49,6 +48,7 @@ import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -71,13 +71,15 @@ final class LocRibWriter implements AutoCloseable, DOMDataTreeChangeListener {
     private final Long ourAs;
     private final RIBSupport ribSupport;
     private final NodeIdentifierWithPredicates tableKey;
+    private final TablesKey localTablesKey;
     private final RIBSupportContextRegistry registry;
     private final ListenerRegistration<LocRibWriter> reg;
 
-    LocRibWriter(final RIBSupportContextRegistry registry, final DOMTransactionChain chain, final YangInstanceIdentifier target, final Long ourAs,
+    private LocRibWriter(final RIBSupportContextRegistry registry, final DOMTransactionChain chain, final YangInstanceIdentifier target, final Long ourAs,
         final DOMDataTreeChangeService service, final PolicyDatabase pd, final TablesKey tablesKey) {
         this.chain = Preconditions.checkNotNull(chain);
         this.tableKey = RibSupportUtils.toYangTablesKey(tablesKey);
+        this.localTablesKey = tablesKey;
         this.locRibTarget = YangInstanceIdentifier.create(target.node(LocRib.QNAME).node(Tables.QNAME).node(this.tableKey).getPathArguments());
         this.ourAs = Preconditions.checkNotNull(ourAs);
         this.registry = registry;
@@ -108,9 +110,9 @@ final class LocRibWriter implements AutoCloseable, DOMDataTreeChangeListener {
         this.chain.close();
     }
 
-    @Nonnull private AbstractRouteEntry createEntry(final PathArgument routeId) {
+    @Nonnull
+    private AbstractRouteEntry createEntry(final PathArgument routeId) {
         final AbstractRouteEntry ret = this.ribSupport.isComplexRoute() ? new ComplexRouteEntry() : new SimpleRouteEntry();
-
         this.routeEntries.put(routeId, ret);
         LOG.trace("Created new entry for {}", routeId);
         return ret;
@@ -118,78 +120,144 @@ final class LocRibWriter implements AutoCloseable, DOMDataTreeChangeListener {
 
     @Override
     public void onDataTreeChanged(final Collection<DataTreeCandidate> changes) {
-        final DOMDataWriteTransaction tx = this.chain.newWriteOnlyTransaction();
-        if (LOG.isTraceEnabled()) {
-            LOG.trace("Received data change to LocRib {}", Arrays.toString(changes.toArray()));
-        }
-
-        /*
-         * We use two-stage processing here in hopes that we avoid duplicate
-         * calculations when multiple peers have changed a particular entry.
-         */
-        final Map<RouteUpdateKey, AbstractRouteEntry> toUpdate = new HashMap<>();
-        update(tx, changes, toUpdate);
+        LOG.trace("Received data change {} to LocRib {}", changes, this);
 
-        // Now walk all updated entries
-        walkThrough(tx, toUpdate);
+        final DOMDataWriteTransaction tx = this.chain.newWriteOnlyTransaction();
+        try {
+            /*
+             * We use two-stage processing here in hopes that we avoid duplicate
+             * calculations when multiple peers have changed a particular entry.
+             */
+            final Map<RouteUpdateKey, AbstractRouteEntry> toUpdate = update(tx, changes);
 
-        tx.submit();
+            // Now walk all updated entries
+            walkThrough(tx, toUpdate);
+        } catch (final Exception e) {
+            LOG.error("Failed to completely propagate updates {}, state is undefined", changes, e);
+        } finally {
+            tx.submit();
+        }
     }
 
-    private void update(final DOMDataWriteTransaction tx, final Collection<DataTreeCandidate> changes,
-        final Map<RouteUpdateKey, AbstractRouteEntry> toUpdate) {
+    private Map<RouteUpdateKey, AbstractRouteEntry> update(final DOMDataWriteTransaction tx,
+        final Collection<DataTreeCandidate> changes) {
+        final Map<RouteUpdateKey, AbstractRouteEntry> ret = new HashMap<>();
 
         for (final DataTreeCandidate tc : changes) {
-            // call out peer-role has changed
             final YangInstanceIdentifier rootPath = tc.getRootPath();
             final DataTreeCandidateNode rootNode = tc.getRootNode();
-            final DataTreeCandidateNode roleChange =  rootNode.getModifiedChild(AbstractPeerRoleTracker.PEER_ROLE_NID);
-            if (roleChange != null) {
-                this.peerPolicyTracker.onDataTreeChanged(roleChange, IdentifierUtils.peerPath(rootPath));
+            //Perform first PeerRoleChange, in case where peer is not deleted, since a new
+            //peer added needs to be add to peerPolicyTracker before process tables
+            if (!rootNode.getModificationType().equals(ModificationType.DELETE)) {
+                filterOutPeerRole(rootNode, rootPath);
             }
-            // filter out any change outside EffRibsIn
-            final DataTreeCandidateNode ribIn = rootNode.getModifiedChild(EFFRIBIN_NID);
-            if (ribIn == null) {
-                LOG.debug("Skipping change {}", tc.getRootNode());
-                continue;
-            }
-            final DataTreeCandidateNode table = ribIn.getModifiedChild(TABLES_NID).getModifiedChild(this.tableKey);
-            if (table == null) {
-                LOG.debug("Skipping change {}", tc.getRootNode());
-                continue;
+            filterOutChangesToSupportedTables(rootNode, rootPath, tx);
+            filterOutAnyChangeOutsideEffRibsIn(rootNode, rootPath, ret, tx);
+            //If Peer is removed we need to filterOutPeerRole after all routes are removed, then peer can
+            // be removed from peerPolicyTracker
+            if (rootNode.getModificationType().equals(ModificationType.DELETE)) {
+                filterOutPeerRole(rootNode, rootPath);
             }
-            final NodeIdentifierWithPredicates peerKey = IdentifierUtils.peerKey(rootPath);
-            final PeerId peerId = IdentifierUtils.peerId(peerKey);
-            final UnsignedInteger routerId = RouterIds.routerIdForPeerId(peerId);
-            for (final DataTreeCandidateNode child : table.getChildNodes()) {
-                if ((Attributes.QNAME).equals(child.getIdentifier().getNodeType())) {
-                    if (child.getDataAfter().isPresent()) {
-                        // putting uptodate attribute in
-                        LOG.trace("Uptodate found for {}", child.getDataAfter());
-                        tx.put(LogicalDatastoreType.OPERATIONAL, this.locRibTarget.node(child.getIdentifier()), child.getDataAfter().get());
+        }
+
+        return ret;
+    }
+
+    private void filterOutAnyChangeOutsideEffRibsIn(final DataTreeCandidateNode rootNode, final YangInstanceIdentifier rootPath,
+        final Map<RouteUpdateKey, AbstractRouteEntry> ret, final DOMDataWriteTransaction tx) {
+        final DataTreeCandidateNode ribIn = rootNode.getModifiedChild(EFFRIBIN_NID);
+        if (ribIn == null) {
+            LOG.debug("Skipping change {}", rootNode.getIdentifier());
+            return;
+        }
+        final DataTreeCandidateNode table = ribIn.getModifiedChild(TABLES_NID).getModifiedChild(this.tableKey);
+        if (table == null) {
+            LOG.debug("Skipping change {}", rootNode.getIdentifier());
+            return;
+        }
+        final NodeIdentifierWithPredicates peerKey = IdentifierUtils.peerKey(rootPath);
+        final PeerId peerId = IdentifierUtils.peerId(peerKey);
+        updateNodes(table, peerId, tx, ret);
+    }
+
+    private void filterOutChangesToSupportedTables(final DataTreeCandidateNode rootNode, final YangInstanceIdentifier rootPath, final DOMDataWriteTransaction tx) {
+        final DataTreeCandidateNode tablesChange = rootNode.getModifiedChild(AbstractPeerRoleTracker.PEER_TABLES);
+
+        if (tablesChange != null) {
+            final PeerId peerIdOfNewPeer = IdentifierUtils.peerId((NodeIdentifierWithPredicates) IdentifierUtils.peerPath(rootPath).getLastPathArgument());
+            final PeerRole newPeerRole = this.peerPolicyTracker.getRole(IdentifierUtils.peerPath(rootPath));
+            final PeerExportGroup peerGroup = this.peerPolicyTracker.getPeerGroup(newPeerRole);
+
+            for (final DataTreeCandidateNode node : tablesChange.getChildNodes()) {
+                final boolean supportedTableAdded = this.peerPolicyTracker.onTablesChanged(peerIdOfNewPeer, node);
+                if (supportedTableAdded) {
+                    for (Map.Entry<PathArgument, AbstractRouteEntry> entry : this.routeEntries.entrySet()) {
+                        if(isTableSupported(peerIdOfNewPeer)) {
+                            final AbstractRouteEntry routeEntry = entry.getValue();
+                            final ContainerNode attributes = routeEntry.attributes();
+                            final PathArgument routeId = entry.getKey();
+                            final YangInstanceIdentifier routeTarget = getRouteTarget(rootPath, routeId);
+                            final NormalizedNode<?, ?> value = routeEntry.createValue(routeId);
+                            final PeerId routePeerId = RouterIds.createPeerId(routeEntry.getBestRouterId());
+                            final ContainerNode effectiveAttributes = peerGroup.effectiveAttributes(routePeerId, attributes);
+
+                            if (effectiveAttributes != null && value != null) {
+                                LOG.debug("Write route {} to peer AdjRibsOut {}", value, peerIdOfNewPeer);
+                                tx.put(LogicalDatastoreType.OPERATIONAL, routeTarget, value);
+                                tx.put(LogicalDatastoreType.OPERATIONAL, routeTarget.node(this.attributesIdentifier), effectiveAttributes);
+                            }
+                        }
                     }
-                    continue;
                 }
-                for (final DataTreeCandidateNode route : this.ribSupport.changedRoutes(child)) {
-                    final PathArgument routeId = route.getIdentifier();
-                    AbstractRouteEntry entry = this.routeEntries.get(routeId);
-
-                    final Optional<NormalizedNode<?, ?>> maybeData = route.getDataAfter();
-                    if (maybeData.isPresent()) {
-                        if (entry == null) {
-                            entry = createEntry(routeId);
-                        }
+            }
+        }
+    }
 
-                        entry.addRoute(routerId, this.attributesIdentifier, maybeData.get());
-                    } else if (entry != null && entry.removeRoute(routerId)) {
-                        this.routeEntries.remove(routeId);
-                        entry = null;
-                        LOG.trace("Removed route from {}", routerId);
-                    }
-                    LOG.debug("Updated route {} entry {}", routeId, entry);
-                    toUpdate.put(new RouteUpdateKey(peerId, routeId), entry);
+    private YangInstanceIdentifier getRouteTarget(final YangInstanceIdentifier rootPath, final PathArgument routeId) {
+        return this.ribSupport.routePath(rootPath.node(AdjRibOut.QNAME).node(Tables.QNAME).node(this.tableKey).node(ROUTES_IDENTIFIER), routeId);
+    }
+
+    private void filterOutPeerRole(final DataTreeCandidateNode rootNode, final YangInstanceIdentifier rootPath) {
+        final DataTreeCandidateNode roleChange = rootNode.getModifiedChild(AbstractPeerRoleTracker.PEER_ROLE_NID);
+        if (roleChange != null) {
+            this.peerPolicyTracker.onDataTreeChanged(roleChange, IdentifierUtils.peerPath(rootPath));
+        }
+    }
+
+    private void updateNodes(final DataTreeCandidateNode table, final PeerId peerId, final DOMDataWriteTransaction tx, final Map<RouteUpdateKey, AbstractRouteEntry> routes) {
+        for (final DataTreeCandidateNode child : table.getChildNodes()) {
+            LOG.debug("Modification type {}", child.getModificationType());
+            if ((Attributes.QNAME).equals(child.getIdentifier().getNodeType())) {
+                if (child.getDataAfter().isPresent()) {
+                    // putting uptodate attribute in
+                    LOG.trace("Uptodate found for {}", child.getDataAfter());
+                    tx.put(LogicalDatastoreType.OPERATIONAL, this.locRibTarget.node(child.getIdentifier()), child.getDataAfter().get());
                 }
+                continue;
             }
+            updateRoutesEntries(child, peerId, routes);
+        }
+    }
+
+    private void updateRoutesEntries(final DataTreeCandidateNode child, final PeerId peerId, final Map<RouteUpdateKey, AbstractRouteEntry> routes) {
+        final UnsignedInteger routerId = RouterIds.routerIdForPeerId(peerId);
+        for (final DataTreeCandidateNode route : this.ribSupport.changedRoutes(child)) {
+            final PathArgument routeId = route.getIdentifier();
+            AbstractRouteEntry entry = this.routeEntries.get(routeId);
+
+            final Optional<NormalizedNode<?, ?>> maybeData = route.getDataAfter();
+            if (maybeData.isPresent()) {
+                if (entry == null) {
+                    entry = createEntry(routeId);
+                }
+                entry.addRoute(routerId, this.attributesIdentifier, maybeData.get());
+            } else if (entry != null && entry.removeRoute(routerId)) {
+                this.routeEntries.remove(routeId);
+                entry = null;
+                LOG.trace("Removed route from {}", routerId);
+            }
+            LOG.debug("Updated route {} entry {}", routeId, entry);
+            routes.put(new RouteUpdateKey(peerId, routeId), entry);
         }
     }
 
@@ -224,6 +292,7 @@ final class LocRibWriter implements AutoCloseable, DOMDataTreeChangeListener {
         }
     }
 
+    @VisibleForTesting
     private void fillAdjRibsOut(final DOMDataWriteTransaction tx, final AbstractRouteEntry entry, final NormalizedNode<?, ?> value, final RouteUpdateKey key) {
         /*
          * We need to keep track of routers and populate adj-ribs-out, too. If we do not, we need to
@@ -237,25 +306,32 @@ final class LocRibWriter implements AutoCloseable, DOMDataTreeChangeListener {
         for (final PeerRole role : PeerRole.values()) {
             final PeerExportGroup peerGroup = this.peerPolicyTracker.getPeerGroup(role);
             if (peerGroup != null) {
-                final ContainerNode attributes = entry == null ? null : entry.attributes();
                 final PeerId peerId = key.getPeerId();
-                final ContainerNode effectiveAttributes = peerGroup.effectiveAttributes(peerId, attributes);
-                for (final Entry<PeerId, YangInstanceIdentifier> pid : peerGroup.getPeers()) {
-                    // This points to adj-rib-out for a particular peer/table combination
-                    final RIBSupportContext ribCtx = this.registry.getRIBSupportContext(this.tableKey);
-                    // FIXME: the table should be created for a peer only once
-                    ribCtx.clearTable(tx, pid.getValue().node(AdjRibOut.QNAME).node(Tables.QNAME).node(this.tableKey));
-                    final YangInstanceIdentifier routeTarget = this.ribSupport.routePath(pid.getValue().node(AdjRibOut.QNAME).node(Tables.QNAME).node(this.tableKey).node(ROUTES_IDENTIFIER), key.getRouteId());
-                    if (effectiveAttributes != null && value != null && !peerId.equals(pid.getKey())) {
-                        LOG.debug("Write route to AdjRibsOut {}", value);
-                        tx.put(LogicalDatastoreType.OPERATIONAL, routeTarget, value);
-                        tx.put(LogicalDatastoreType.OPERATIONAL, routeTarget.node(this.attributesIdentifier), effectiveAttributes);
-                    } else {
-                        LOG.trace("Removing {} from transaction", routeTarget);
-                        tx.delete(LogicalDatastoreType.OPERATIONAL, routeTarget);
+                final ContainerNode effectiveAttributes = peerGroup.effectiveAttributes(peerId, entry.attributes());
+                if (effectiveAttributes != null) {
+                    for (final Entry<PeerId, YangInstanceIdentifier> pid : peerGroup.getPeers()) {
+                        if (!peerId.equals(pid.getKey()) && isTableSupported(pid.getKey())) {
+                            final YangInstanceIdentifier routeTarget = getRouteTarget(pid.getValue(), key.getRouteId());
+                            if (value != null) {
+                                LOG.debug("Write route {} to peers AdjRibsOut {}", value, pid.getKey());
+                                tx.put(LogicalDatastoreType.OPERATIONAL, routeTarget, value);
+                                tx.put(LogicalDatastoreType.OPERATIONAL, routeTarget.node(this.attributesIdentifier), effectiveAttributes);
+                            } else {
+                                LOG.trace("Removing {} from transaction for peer {}", routeTarget, pid.getKey());
+                                tx.delete(LogicalDatastoreType.OPERATIONAL, routeTarget);
+                            }
+                        }
                     }
                 }
             }
         }
     }
+
+    private boolean isTableSupported(final PeerId key) {
+        if (!this.peerPolicyTracker.isTableSupported(key, this.localTablesKey)) {
+            LOG.trace("Route rejected, peer {} does not support this table type {}", key, this.localTablesKey);
+            return false;
+        }
+        return true;
+    }
 }