*/
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;
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;
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;
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;
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;
@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);
}
}
}
}
+ @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
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;
+ }
}