*/
package org.opendaylight.protocol.bgp.rib.impl;
-import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
-import com.google.common.base.Predicate;
-import com.google.common.collect.Iterables;
+import com.google.common.base.Verify;
import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
import javax.annotation.concurrent.NotThreadSafe;
import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeChangeListener;
import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
import org.opendaylight.protocol.bgp.rib.spi.RIBExtensionConsumerContext;
import org.opendaylight.protocol.bgp.rib.spi.RIBSupport;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.PeerId;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.PeerRole;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.multiprotocol.rev130919.open.bgp.parameters.optional.capabilities.c.parameters.graceful.restart._case.graceful.restart.capability.Tables;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.bgp.rib.rib.Peer;
-import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.rib.Tables;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.bgp.rib.rib.peer.AdjRibIn;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.bgp.rib.rib.peer.EffectiveRibIn;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.rib.tables.Routes;
import org.opendaylight.yangtools.concepts.ListenerRegistration;
-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.api.YangInstanceIdentifier.PathArgument;
import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
-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.NormalizedNodes;
import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
* structures. This is done so we maintain causality and loose coupling.
*/
@NotThreadSafe
-final class EffectiveRibInWriter {
- private static final Predicate<PathArgument> IS_PEER = new Predicate<PathArgument>() {
- @Override
- public boolean apply(final PathArgument input) {
- return input.getNodeType().equals(Peer.QNAME);
- }
- };
- private static final Predicate<PathArgument> IS_TABLES = new Predicate<PathArgument>() {
- @Override
- public boolean apply(final PathArgument input) {
- return input.getNodeType().equals(Tables.QNAME);
- }
- };
+final class EffectiveRibInWriter implements AutoCloseable {
private static final Logger LOG = LoggerFactory.getLogger(EffectiveRibInWriter.class);
- private static final QName PEER_ID = QName.create(Peer.QNAME, "peer-id");
-
- // FIXME: implement as id.firstIdentifierOf(IS_PEER), null indicating not found
- private static final NodeIdentifierWithPredicates firstKeyOf(final YangInstanceIdentifier id, final Predicate<PathArgument> match) {
- final PathArgument ret = Iterables.find(id.getPathArguments(), IS_PEER);
- Preconditions.checkArgument(ret instanceof NodeIdentifierWithPredicates, "Non-key peer identifier %s", ret);
- return (NodeIdentifierWithPredicates) ret;
- }
-
- static final NodeIdentifierWithPredicates peerKey(final YangInstanceIdentifier id) {
- return firstKeyOf(id, IS_PEER);
- }
-
- static final PeerId peerId(final NodeIdentifierWithPredicates peerKey) {
- return (PeerId) peerKey.getKeyValues().get(PEER_ID);
- }
-
- private static final NodeIdentifierWithPredicates tableKey(final YangInstanceIdentifier id) {
- return firstKeyOf(id, IS_TABLES);
- }
+ private static final NodeIdentifier TABLE_ROUTES = new NodeIdentifier(Routes.QNAME);
/**
- * Maintains the mapping of PeerId -> Role inside. We are subscribed to our target leaf,
- * but that is a wildcard:
- * /bgp-rib/rib/peer/peer-role
- *
- * MD-SAL assumption: we are getting one {@link DataTreeCandidate} for each expanded
- * wildcard path, so are searching for a particular key.
- */
- private final class PeerRoleListener implements DOMDataTreeChangeListener {
- @Override
- public void onDataTreeChanged(final Collection<DataTreeCandidate> changes) {
- synchronized (policies) {
- for (DataTreeCandidate tc : changes) {
- // Obtain the peer's key
- final NodeIdentifierWithPredicates peerKey = peerKey(tc.getRootPath());
-
- // Check for removal
- final Optional<NormalizedNode<?, ?>> maybePeerRole = tc.getRootNode().getDataAfter();
- if (maybePeerRole.isPresent()) {
- final LeafNode<?> peerRoleLeaf = (LeafNode<?>) maybePeerRole.get();
- // FIXME: need codec here
- final PeerRole peerRole = (PeerRole) peerRoleLeaf.getValue();
-
- // Lookup policy based on role
- final AbstractImportPolicy policy = AbstractImportPolicy.forRole(peerRole);
-
- // Update lookup map
- policies.put(peerId(peerKey), policy);
- } else {
- policies.remove(peerId(peerKey));
- }
- }
- }
- }
- }
-
- /**
- * Maintains the individual routes for a particular table's routes under:
- * /bgp-rib/rib/peer/adj-rib-in/tables/routes
+ * Maintains {@link TableRouteListener} instances.
*/
- private final class TableRouteListener implements DOMDataTreeChangeListener {
- private final NodeIdentifierWithPredicates tableKey;
- private final YangInstanceIdentifier target;
- private final RIBSupport ribSupport;
- private final PeerId peerId;
-
- TableRouteListener(final RIBSupport ribSupport, final NodeIdentifierWithPredicates peerKey, final NodeIdentifierWithPredicates tableKey) {
- this.ribSupport = Preconditions.checkNotNull(ribSupport);
- this.tableKey = Preconditions.checkNotNull(tableKey);
-
- // Lookup peer ID
- this.peerId = (PeerId) Preconditions.checkNotNull(peerKey.getKeyValues().get(PEER_ID));
-
- // FIXME: need target table ID
- target = null;
- }
-
- private void updateRoutes(final DOMDataWriteTransaction tx, final DataTreeCandidateNode routes, final ContainerNode effectiveAttrs) {
- final YangInstanceIdentifier routeId = target.node(routes.getIdentifier());
+ private final class AdjInTracker implements AutoCloseable, DOMDataTreeChangeListener {
+ private final RIBExtensionConsumerContext registry;
+ private final YangInstanceIdentifier ribId;
+ private final ListenerRegistration<?> reg;
+ private final DOMTransactionChain chain;
- if (effectiveAttrs != null) {
- tx.put(LogicalDatastoreType.OPERATIONAL, routeId, routes.getDataAfter().get());
- tx.put(LogicalDatastoreType.OPERATIONAL, routeId.node(ribSupport.routeAttributes()), effectiveAttrs);
- } else if (routes.getDataBefore().isPresent()) {
- tx.delete(LogicalDatastoreType.OPERATIONAL, routeId);
- }
+ AdjInTracker(final DOMDataTreeChangeService service, final RIBExtensionConsumerContext registry, final DOMTransactionChain chain, final YangInstanceIdentifier ribId) {
+ this.registry = Preconditions.checkNotNull(registry);
+ this.chain = Preconditions.checkNotNull(chain);
+ this.ribId = Preconditions.checkNotNull(ribId);
+ final YangInstanceIdentifier tableId = ribId.node(Peer.QNAME).node(AdjRibIn.QNAME).node(Tables.QNAME);
+ final DOMDataTreeIdentifier treeId = new DOMDataTreeIdentifier(LogicalDatastoreType.OPERATIONAL, tableId);
+ this.reg = service.registerDataTreeChangeListener(treeId, this);
}
- @Override
- public void onDataTreeChanged(final Collection<DataTreeCandidate> changes) {
- // FIXME: note that we need to detect table clears efficiently and propagate them
-
- final DOMDataWriteTransaction tx = chain.newWriteOnlyTransaction();
-
- for (DataTreeCandidate tc : changes) {
+ private void processRoute(final DOMDataWriteTransaction tx, final RIBSupport ribSupport, final AbstractImportPolicy policy, final YangInstanceIdentifier routesPath, final DataTreeCandidateNode route) {
+ switch (route.getModificationType()) {
+ case DELETE:
+ // Delete has already been affected by the store in caller, so this is a no-op.
+ break;
+ case MERGE:
+ LOG.info("Merge on {} reported, this should never have happened, ignoring", route);
+ break;
+ case UNMODIFIED:
+ // No-op
+ break;
+ case SUBTREE_MODIFIED:
+ case WRITE:
// Lookup per-table attributes from RIBSupport
- final ContainerNode adverisedAttrs = (ContainerNode) NormalizedNodes.findNode(tc.getRootNode().getDataAfter(), ribSupport.routeAttributes()).orNull();
+ final ContainerNode adverisedAttrs = (ContainerNode) NormalizedNodes.findNode(route.getDataAfter(), ribSupport.routeAttributesIdentifier()).orNull();
final ContainerNode effectiveAttrs;
- if (adverisedAttrs != null && tc.getRootNode().getDataAfter().isPresent()) {
- synchronized (policies) {
- final AbstractImportPolicy policy = policies.get(peerId);
- effectiveAttrs = policy.effectiveAttributes(adverisedAttrs);
+ if (adverisedAttrs != null) {
+ effectiveAttrs = policy.effectiveAttributes(adverisedAttrs);
+
+ /*
+ * Speed hack: if we determine that the policy has passed the attributes
+ * back unmodified, the corresponding change has already been written in
+ * our caller. There is no need to perform any further processing.
+ *
+ * We also use direct object comparison to make the check very fast, as
+ * it may not be that common, in which case it does not make sense to pay
+ * the full equals price.
+ */
+ if (effectiveAttrs == adverisedAttrs) {
+ return;
}
} else {
effectiveAttrs = null;
}
- LOG.debug("Route change {} effective attributes {}", tc.getRootPath(), effectiveAttrs);
+ final YangInstanceIdentifier routeId = ribSupport.routePath(routesPath, route.getIdentifier());
+ LOG.debug("Route {} effective attributes {} towards {}", route.getIdentifier(), effectiveAttrs, routeId);
- updateRoutes(tx, tc.getRootNode(), effectiveAttrs);
+ if (effectiveAttrs != null) {
+ tx.put(LogicalDatastoreType.OPERATIONAL, routeId.node(ribSupport.routeAttributesIdentifier()), effectiveAttrs);
+ } else {
+ LOG.warn("Route {} advertised empty attributes", route.getDataAfter());
+ tx.delete(LogicalDatastoreType.OPERATIONAL, routeId);
+ }
+ break;
+ default:
+ LOG.warn("Ignoring unhandled route {}", route);
+ break;
}
+ }
- tx.submit();
+ private void processTableChildren(final DOMDataWriteTransaction tx, final RIBSupport ribSupport, final AbstractImportPolicy policy, final YangInstanceIdentifier tablePath, final Collection<DataTreeCandidateNode> children) {
+ for (DataTreeCandidateNode child : children) {
+ switch (child.getModificationType()) {
+ case DELETE:
+ tx.delete(LogicalDatastoreType.OPERATIONAL, tablePath.node(child.getIdentifier()));
+ break;
+ case MERGE:
+ LOG.info("Merge on {} reported, this should never have happened, ignoring", child);
+ break;
+ case UNMODIFIED:
+ // No-op
+ break;
+ case SUBTREE_MODIFIED:
+ case WRITE:
+ tx.put(LogicalDatastoreType.OPERATIONAL, tablePath.node(child.getIdentifier()), child.getDataAfter().get());
+
+ // Routes are special, as they may end up being filtered. The previous put conveniently
+ // ensured that we have them in at target, so a subsequent delete will not fail :)
+ if (TABLE_ROUTES.equals(child.getIdentifier())) {
+ final YangInstanceIdentifier routesPath = tablePath.node(Routes.QNAME);
+ for (DataTreeCandidateNode route : ribSupport.changedRoutes(child)) {
+ processRoute(tx, ribSupport, policy, routesPath, route);
+ }
+ }
+ break;
+ default:
+ LOG.warn("Ignoring unhandled child {}", child);
+ break;
+ }
+ }
}
- }
- /**
- * Maintains {@link TableRouteListener} instances.
- */
- private final class TableListener implements DOMDataTreeChangeListener {
- private final Map<YangInstanceIdentifier, ListenerRegistration<?>> routeListeners = new HashMap<>();
- private final RIBExtensionConsumerContext registry;
- private final DOMDataTreeChangeService service;
+ private RIBSupport getRibSupport(final NodeIdentifierWithPredicates tableKey) {
+ // FIXME: use codec to translate tableKey
+ return registry.getRIBSupport(null);
+ }
- TableListener(final DOMDataTreeChangeService service, final RIBExtensionConsumerContext registry) {
- this.registry = Preconditions.checkNotNull(registry);
- this.service = Preconditions.checkNotNull(service);
+ private YangInstanceIdentifier effectiveTablePath(final NodeIdentifierWithPredicates peerKey, final NodeIdentifierWithPredicates tableKey) {
+ return ribId.node(peerKey).node(EffectiveRibIn.QNAME).node(tableKey);
+ }
+
+ private void modifyTable(final DOMDataWriteTransaction tx, final NodeIdentifierWithPredicates peerKey, final NodeIdentifierWithPredicates tableKey, final DataTreeCandidateNode table) {
+ final RIBSupport ribSupport = getRibSupport(tableKey);
+ final YangInstanceIdentifier tablePath = effectiveTablePath(peerKey, tableKey);
+
+ final AbstractImportPolicy policy = peerPolicyTracker.policyFor(IdentifierUtils.peerId(peerKey));
+ processTableChildren(tx, ribSupport, policy, tablePath, table.getChildNodes());
+ }
+
+ private void writeTable(final DOMDataWriteTransaction tx, final NodeIdentifierWithPredicates peerKey, final NodeIdentifierWithPredicates tableKey, final DataTreeCandidateNode table) {
+ final RIBSupport ribSupport = getRibSupport(tableKey);
+ final YangInstanceIdentifier tablePath = effectiveTablePath(peerKey, tableKey);
+
+ // Create an empty table
+ TableContext.clearTable(tx, ribSupport, tablePath);
+
+ final AbstractImportPolicy policy = peerPolicyTracker.policyFor(IdentifierUtils.peerId(peerKey));
+ processTableChildren(tx, ribSupport, policy, tablePath, table.getChildNodes());
}
@Override
public void onDataTreeChanged(final Collection<DataTreeCandidate> changes) {
+ final DOMDataWriteTransaction tx = chain.newWriteOnlyTransaction();
for (DataTreeCandidate tc : changes) {
+ final YangInstanceIdentifier rootPath = tc.getRootPath();
+
// Obtain the peer's key
- final NodeIdentifierWithPredicates peerKey = peerKey(tc.getRootPath());
+ final NodeIdentifierWithPredicates peerKey = IdentifierUtils.peerKey(rootPath);
- // Lookup
- final NodeIdentifierWithPredicates tableKey = tableKey(tc.getRootPath());
+ // Extract the table key, this should be safe based on the path where we subscribed,
+ // but let's verify explicitly.
+ final PathArgument lastArg = rootPath.getLastPathArgument();
+ Verify.verify(lastArg instanceof NodeIdentifierWithPredicates, "Unexpected type %s in path %s", lastArg.getClass(), rootPath);
+ final NodeIdentifierWithPredicates tableKey = (NodeIdentifierWithPredicates) lastArg;
- switch (tc.getRootNode().getModificationType()) {
+ final DataTreeCandidateNode root = tc.getRootNode();
+ switch (root.getModificationType()) {
case DELETE:
- final ListenerRegistration<?> reg = routeListeners.remove(tc.getRootPath());
- if (reg != null) {
- reg.close();
- }
+ // delete the corresponding effective table
+ tx.delete(LogicalDatastoreType.OPERATIONAL, effectiveTablePath(peerKey, tableKey));
+ break;
+ case MERGE:
+ // TODO: upstream API should never give us this, as it leaks how the delta was created.
+ LOG.info("Merge on {} reported, this should never have happened, but attempting to cope", rootPath);
+ modifyTable(tx, peerKey, tableKey, root);
+ break;
+ case SUBTREE_MODIFIED:
+ modifyTable(tx, peerKey, tableKey, root);
+ break;
+ case UNMODIFIED:
+ LOG.info("Ignoring spurious notification on {} data {}", rootPath, root);
break;
case WRITE:
- // FIXME: use codec to translate
- final RIBSupport ribSupport = registry.getRIBSupport(null);
- if (ribSupport != null) {
- final TableRouteListener routeListener = new TableRouteListener(ribSupport, peerKey, tableKey);
- final ListenerRegistration<?> r = service.registerDataTreeChangeListener(
- new DOMDataTreeIdentifier(LogicalDatastoreType.OPERATIONAL, tc.getRootPath()), routeListener);
-
- routeListeners.put(tc.getRootPath(), r);
- } else {
- LOG.warn("No RIB support for table {}, ignoring advertisements from peer %s", tableKey, peerKey);
- }
+ writeTable(tx, peerKey, tableKey, root);
break;
default:
- // No-op
+ LOG.warn("Ignoring unhandled root {}", root);
break;
}
}
+
+ tx.submit();
+ }
+
+ @Override
+ public void close() {
+ // FIXME: wipe all effective routes?
+ reg.close();
}
}
- private final Map<PeerId, AbstractImportPolicy> policies = new HashMap<>();
- private final DOMTransactionChain chain;
+ private final ImportPolicyPeerTracker peerPolicyTracker;
+ private final AdjInTracker adjInTracker;
- private EffectiveRibInWriter(final DOMTransactionChain chain) {
- this.chain = Preconditions.checkNotNull(chain);
+ private EffectiveRibInWriter(final DOMDataTreeChangeService service, final DOMTransactionChain chain, final YangInstanceIdentifier ribId) {
+ this.peerPolicyTracker = new ImportPolicyPeerTracker(service, ribId);
+ // FIXME: proper argument
+ this.adjInTracker = new AdjInTracker(service, null, chain, ribId);
+ }
- // FIXME: subscribe peerRoleListener, tableListener
+ @Override
+ public void close() {
+ adjInTracker.close();
+ peerPolicyTracker.close();
}
}