+
+ @Holding("this")
+ private void changeDataTree(final DOMDataTreeWriteTransaction tx, final YangInstanceIdentifier rootPath,
+ final DataTreeCandidateNode root, final DataTreeCandidateNode table) {
+ final PathArgument lastArg = table.getIdentifier();
+ verify(lastArg instanceof NodeIdentifierWithPredicates, "Unexpected type %s in path %s", lastArg.getClass(),
+ rootPath);
+ final NodeIdentifierWithPredicates tableKey = (NodeIdentifierWithPredicates) lastArg;
+ final RIBSupportContext ribContext = this.registry.getRIBSupportContext(tableKey);
+ if (ribContext == null) {
+ LOG.warn("Table {} is not supported, ignoring event", tableKey);
+ return;
+ }
+
+ final YangInstanceIdentifier effectiveTablePath = effectiveTablePath(tableKey);
+ final ModificationType modificationType = root.getModificationType();
+ LOG.debug("Effective table {} modification type {}", effectiveTablePath, modificationType);
+ switch (modificationType) {
+ case DISAPPEARED:
+ case DELETE:
+ deleteTable(tx, ribContext, effectiveTablePath, table);
+ break;
+ case APPEARED:
+ case WRITE:
+ writeTable(tx, ribContext, effectiveTablePath, table);
+ break;
+ case SUBTREE_MODIFIED:
+ modifyTable(tx, ribContext, effectiveTablePath, table);
+ break;
+ case UNMODIFIED:
+ LOG.info("Ignoring spurious notification on {} data {}", rootPath, table);
+ break;
+ default:
+ LOG.warn("Ignoring unhandled root {}", table);
+ break;
+ }
+ }
+
+ private void deleteTable(final DOMDataTreeWriteTransaction tx, final RIBSupportContext ribContext,
+ final YangInstanceIdentifier effectiveTablePath, final DataTreeCandidateNode table) {
+ LOG.debug("Delete Effective Table {}", effectiveTablePath);
+ onDeleteTable(ribContext.getRibSupport(), effectiveTablePath, table.getDataBefore());
+ tx.delete(LogicalDatastoreType.OPERATIONAL, effectiveTablePath);
+ }
+
+ private void modifyTable(final DOMDataTreeWriteTransaction tx, final RIBSupportContext ribContext,
+ final YangInstanceIdentifier effectiveTablePath, final DataTreeCandidateNode table) {
+ LOG.debug("Modify Effective Table {}", effectiveTablePath);
+
+ final boolean wasLongLivedStale = isLongLivedStaleTable(table.getDataBefore());
+ final boolean longLivedStale = isLongLivedStaleTable(table.getDataAfter());
+ if (wasLongLivedStale != longLivedStale) {
+ LOG.debug("LLGR_STALE flag flipped {}, overwriting table {}", longLivedStale ? "ON" : "OFF",
+ effectiveTablePath);
+ writeTable(tx, ribContext, effectiveTablePath, table);
+ return;
+ }
+
+ table.getModifiedChild(ATTRIBUTES_NID).ifPresent(modifiedAttrs -> {
+ final YangInstanceIdentifier effAttrsPath = effectiveTablePath.node(ATTRIBUTES_NID);
+ final Optional<NormalizedNode<?, ?>> optAttrsAfter = modifiedAttrs.getDataAfter();
+ if (optAttrsAfter.isPresent()) {
+ tx.put(LogicalDatastoreType.OPERATIONAL, effAttrsPath, effectiveAttributes(
+ NormalizedNodes.findNode(optAttrsAfter.get(), UPTODATE_NID)));
+ } else {
+ tx.delete(LogicalDatastoreType.OPERATIONAL, effAttrsPath);
+ }
+ });
+
+ table.getModifiedChild(ROUTES_NID).ifPresent(modifiedRoutes -> {
+ final RIBSupport<?, ?, ?, ?> ribSupport = ribContext.getRibSupport();
+ switch (modifiedRoutes.getModificationType()) {
+ case APPEARED:
+ case WRITE:
+ deleteRoutesBefore(tx, ribSupport, effectiveTablePath, modifiedRoutes);
+ // XXX: YANG Tools seems to have an issue stacking DELETE with child WRITE
+ tx.put(LogicalDatastoreType.OPERATIONAL, effectiveTablePath.node(ROUTES_NID), EMPTY_ROUTES);
+ writeRoutesAfter(tx, ribSupport, effectiveTablePath, modifiedRoutes.getDataAfter(), longLivedStale);
+ break;
+ case DELETE:
+ case DISAPPEARED:
+ deleteRoutesBefore(tx, ribSupport, effectiveTablePath, modifiedRoutes);
+ tx.delete(LogicalDatastoreType.OPERATIONAL, effectiveTablePath.node(ROUTES_NID));
+ break;
+ case SUBTREE_MODIFIED:
+ for (DataTreeCandidateNode modifiedRoute : ribSupport.changedRoutes(modifiedRoutes)) {
+ processRoute(tx, ribSupport, effectiveTablePath, modifiedRoute, longLivedStale);
+ }
+ break;
+ case UNMODIFIED:
+ // No-op
+ return;
+ default:
+ LOG.warn("Ignoring modified routes {}", modifiedRoutes);
+ break;
+ }
+ });
+ }
+
+ private void writeTable(final DOMDataTreeWriteTransaction tx, final RIBSupportContext ribContext,
+ final YangInstanceIdentifier effectiveTablePath, final DataTreeCandidateNode table) {
+ LOG.debug("Write Effective Table {}", effectiveTablePath);
+ onDeleteTable(ribContext.getRibSupport(), effectiveTablePath, table.getDataBefore());
+
+ final Optional<NormalizedNode<?, ?>> maybeTableAfter = table.getDataAfter();
+ if (maybeTableAfter.isPresent()) {
+ final MapEntryNode tableAfter = extractMapEntry(maybeTableAfter);
+ ribContext.createEmptyTableStructure(tx, effectiveTablePath);
+
+ final Optional<DataContainerChild<?, ?>> maybeAttrsAfter = tableAfter.getChild(ATTRIBUTES_NID);
+ final boolean longLivedStale;
+ if (maybeAttrsAfter.isPresent()) {
+ final ContainerNode attrsAfter = extractContainer(maybeAttrsAfter);
+ longLivedStale = isLongLivedStale(attrsAfter);
+ tx.put(LogicalDatastoreType.OPERATIONAL, effectiveTablePath.node(ATTRIBUTES_NID),
+ effectiveAttributes(attrsAfter.getChild(UPTODATE_NID)));
+ } else {
+ longLivedStale = false;
+ }
+
+ writeRoutesAfter(tx, ribContext.getRibSupport(), effectiveTablePath,
+ NormalizedNodes.findNode(tableAfter, ROUTES_NID), longLivedStale);
+ }
+ }
+
+ // Performs house-keeping when the contents of a table is deleted
+ private void onDeleteTable(final RIBSupport<?, ?, ?, ?> ribSupport, final YangInstanceIdentifier effectiveTablePath,
+ final Optional<NormalizedNode<?, ?>> tableBefore) {
+ // Routes are special in that we need to process the to keep our counters accurate
+ final Optional<NormalizedNode<?, ?>> maybeRoutesBefore = findRoutesMap(ribSupport,
+ NormalizedNodes.findNode(tableBefore, ROUTES_NID));
+ if (maybeRoutesBefore.isPresent()) {
+ onRoutesDeleted(ribSupport, effectiveTablePath, extractMap(maybeRoutesBefore).getValue());
+ }
+ }
+
+ private void deleteRoutesBefore(final DOMDataTreeWriteTransaction tx, final RIBSupport<?, ?, ?, ?> ribSupport,
+ final YangInstanceIdentifier effectiveTablePath, final DataTreeCandidateNode modifiedRoutes) {
+ final Optional<NormalizedNode<?, ?>> maybeRoutesBefore = NormalizedNodes.findNode(
+ modifiedRoutes.getDataBefore(), ribSupport.relativeRoutesPath());
+ if (maybeRoutesBefore.isPresent()) {
+ onRoutesDeleted(ribSupport, effectiveTablePath, extractMap(maybeRoutesBefore).getValue());
+ }
+ }
+
+ private void writeRoutesAfter(final DOMDataTreeWriteTransaction tx, final RIBSupport<?, ?, ?, ?> ribSupport,
+ final YangInstanceIdentifier effectiveTablePath, final Optional<NormalizedNode<?, ?>> routesAfter,
+ final boolean longLivedStale) {
+ final Optional<NormalizedNode<?, ?>> maybeRoutesAfter = NormalizedNodes.findNode(routesAfter,
+ ribSupport.relativeRoutesPath());
+ if (maybeRoutesAfter.isPresent()) {
+ final YangInstanceIdentifier routesPath = routeMapPath(ribSupport, effectiveTablePath);
+ for (MapEntryNode routeAfter : extractMap(maybeRoutesAfter).getValue()) {
+ writeRoute(tx, ribSupport, routesPath.node(routeAfter.getIdentifier()), Optional.empty(), routeAfter,
+ longLivedStale);
+ }
+ }
+ }
+
+ private void onRoutesDeleted(final RIBSupport<?, ?, ?, ?> ribSupport,
+ final YangInstanceIdentifier effectiveTablePath, final Collection<MapEntryNode> deletedRoutes) {
+ if (ribSupport.getSafi() == RouteTargetConstrainSubsequentAddressFamily.class) {
+ final YangInstanceIdentifier routesPath = routeMapPath(ribSupport, effectiveTablePath);
+ for (final MapEntryNode routeBefore : deletedRoutes) {
+ deleteRouteTarget(ribSupport, routesPath.node(routeBefore.getIdentifier()), routeBefore);
+ }
+ this.rtMembershipsUpdated = true;
+ }
+
+ final TablesKey tablesKey = ribSupport.getTablesKey();
+ CountersUtil.add(prefixesInstalled.get(tablesKey), tablesKey, -deletedRoutes.size());
+ }
+
+ private void processRoute(final DOMDataTreeWriteTransaction tx, final RIBSupport<?, ?, ?, ?> ribSupport,
+ final YangInstanceIdentifier routesPath, final DataTreeCandidateNode route, final boolean longLivedStale) {
+ LOG.debug("Process route {}", route.getIdentifier());
+ final YangInstanceIdentifier routePath = ribSupport.routePath(routesPath, route.getIdentifier());
+ switch (route.getModificationType()) {
+ case DELETE:
+ case DISAPPEARED:
+ deleteRoute(tx, ribSupport, routePath, route.getDataBefore().orElse(null));
+ break;
+ case UNMODIFIED:
+ // No-op
+ break;
+ case APPEARED:
+ case SUBTREE_MODIFIED:
+ case WRITE:
+ writeRoute(tx, ribSupport, routePath, route.getDataBefore(), route.getDataAfter().get(),
+ longLivedStale);
+ break;
+ default:
+ LOG.warn("Ignoring unhandled route {}", route);
+ break;
+ }
+ }
+
+ private void deleteRoute(final DOMDataTreeWriteTransaction tx, final RIBSupport<?, ?, ?, ?> ribSupport,
+ final YangInstanceIdentifier routeIdPath, final NormalizedNode<?, ?> route) {
+ handleRouteTarget(ModificationType.DELETE, ribSupport, routeIdPath, route);
+ tx.delete(LogicalDatastoreType.OPERATIONAL, routeIdPath);
+ LOG.debug("Route deleted. routeId={}", routeIdPath);
+ final TablesKey tablesKey = ribSupport.getTablesKey();
+ CountersUtil.decrement(this.prefixesInstalled.get(tablesKey), tablesKey);
+ }
+
+ private void writeRoute(final DOMDataTreeWriteTransaction tx, final RIBSupport<?, ?, ?, ?> ribSupport,
+ final YangInstanceIdentifier routePath, final Optional<NormalizedNode<?, ?>> routeBefore,
+ final NormalizedNode<?, ?> routeAfter, final boolean longLivedStale) {
+ final TablesKey tablesKey = ribSupport.getTablesKey();
+ CountersUtil.increment(this.prefixesReceived.get(tablesKey), tablesKey);
+ // Lookup per-table attributes from RIBSupport
+ final ContainerNode advertisedAttrs = (ContainerNode) NormalizedNodes.findNode(routeAfter,
+ ribSupport.routeAttributesIdentifier()).orElse(null);
+ final Attributes routeAttrs = ribSupport.attributeFromContainerNode(advertisedAttrs);
+ final Optional<Attributes> optEffAtt;
+ // In case we want to add LLGR_STALE we do not process route through policies since it may be
+ // considered as received with LLGR_STALE from peer which is not true.
+ if (longLivedStale) {
+ // LLGR procedures are in effect. If the route is tagged with NO_LLGR, it needs to be removed.
+ final List<Communities> effCommunities = routeAttrs.getCommunities();
+ if (effCommunities != null && effCommunities.contains(CommunityUtil.NO_LLGR)) {
+ deleteRoute(tx, ribSupport, routePath, routeBefore.orElse(null));
+ return;
+ }
+ optEffAtt = Optional.of(wrapLongLivedStale(routeAttrs));
+ } else {
+ final Class<? extends AfiSafiType> afiSafiType
+ = tableTypeRegistry.getAfiSafiType(ribSupport.getTablesKey()).get();
+ optEffAtt = this.ribPolicies
+ .applyImportPolicies(this.peerImportParameters, routeAttrs, afiSafiType);
+ }
+ if (!optEffAtt.isPresent()) {
+ deleteRoute(tx, ribSupport, routePath, routeBefore.orElse(null));
+ return;
+ }
+ handleRouteTarget(ModificationType.WRITE, ribSupport, routePath, routeAfter);
+ tx.put(LogicalDatastoreType.OPERATIONAL, routePath, routeAfter);
+ CountersUtil.increment(this.prefixesInstalled.get(tablesKey), tablesKey);
+
+ final YangInstanceIdentifier attPath = routePath.node(ribSupport.routeAttributesIdentifier());
+ final Attributes attToStore = optEffAtt.get();
+ if (!attToStore.equals(routeAttrs)) {
+ final ContainerNode finalAttribute = ribSupport.attributeToContainerNode(attPath, attToStore);
+ tx.put(LogicalDatastoreType.OPERATIONAL, attPath, finalAttribute);
+ }
+ }
+
+ private void addRouteTarget(final RouteTargetConstrainRoute rtc) {
+ final RouteTarget rtMembership = RouteTargetMembeshipUtil.getRT(rtc);
+ if (PeerRole.Ebgp != this.peerImportParameters.getFromPeerRole()) {
+ this.rtCache.cacheRoute(rtc);
+ }
+ this.rtMemberships.add(rtMembership);
+ }
+
+ private void deleteRouteTarget(final RIBSupport<?, ?, ?, ?> ribSupport, final YangInstanceIdentifier routeIdPath,
+ final NormalizedNode<?, ?> route) {
+ deleteRouteTarget((RouteTargetConstrainRoute) ribSupport.fromNormalizedNode(routeIdPath, route));
+ }
+
+ private void deleteRouteTarget(final RouteTargetConstrainRoute rtc) {
+ final RouteTarget rtMembership = RouteTargetMembeshipUtil.getRT(rtc);
+ if (PeerRole.Ebgp != this.peerImportParameters.getFromPeerRole()) {
+ this.rtCache.uncacheRoute(rtc);
+ }
+ this.rtMemberships.remove(rtMembership);
+ }
+
+ private void handleRouteTarget(final ModificationType modificationType, final RIBSupport<?, ?, ?, ?> ribSupport,
+ final YangInstanceIdentifier routeIdPath, final NormalizedNode<?, ?> route) {
+ if (ribSupport.getSafi() == RouteTargetConstrainSubsequentAddressFamily.class) {
+ final RouteTargetConstrainRoute rtc =
+ (RouteTargetConstrainRoute) ribSupport.fromNormalizedNode(routeIdPath, route);
+ if (ModificationType.DELETE == modificationType) {
+ deleteRouteTarget(rtc);
+ } else {
+ addRouteTarget(rtc);
+ }
+ this.rtMembershipsUpdated = true;
+ }
+ }
+
+ @SuppressFBWarnings("UPM_UNCALLED_PRIVATE_METHOD")
+ private static Attributes wrapLongLivedStale(final Attributes attrs) {
+ if (attrs == null) {
+ return STALE_LLGR_ATTRIBUTES;
+ }
+
+ final List<Communities> oldCommunities = attrs.getCommunities();
+ final List<Communities> newCommunities;
+ if (oldCommunities != null) {
+ if (oldCommunities.contains(StaleCommunities.STALE_LLGR)) {
+ return attrs;
+ }
+ newCommunities = StaleCommunities.create(oldCommunities);
+ } else {
+ newCommunities = STALE_LLGR_COMMUNUTIES;
+ }
+
+ return new org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev200120
+ .path.attributes.AttributesBuilder(attrs).setCommunities(newCommunities).build();
+ }
+
+ // XXX: this should be moved to YangInstanceIdentifier at some point
+ private static YangInstanceIdentifier concat(final YangInstanceIdentifier parent, final List<PathArgument> args) {
+ YangInstanceIdentifier ret = parent;
+ for (PathArgument arg : args) {
+ ret = ret.node(arg);
+ }
+ return ret;
+ }
+
+ private YangInstanceIdentifier effectiveTablePath(final NodeIdentifierWithPredicates tableKey) {
+ return this.effRibTables.node(TABLES_NID).node(tableKey);
+ }
+
+ private static YangInstanceIdentifier routeMapPath(final RIBSupport<?, ?, ?, ?> ribSupport,
+ final YangInstanceIdentifier tablePath) {
+ return concat(tablePath.node(ROUTES_NID), ribSupport.relativeRoutesPath());
+ }
+
+ private static Optional<NormalizedNode<?, ?>> findRoutesMap(final RIBSupport<?, ?, ?, ?> ribSupport,
+ final Optional<NormalizedNode<?, ?>> optRoutes) {
+ return NormalizedNodes.findNode(optRoutes, ribSupport.relativeRoutesPath());
+ }
+
+ private static ContainerNode extractContainer(final Optional<? extends NormalizedNode<?, ?>> optNode) {
+ final NormalizedNode<?, ?> node = optNode.get();
+ verify(node instanceof ContainerNode, "Expected ContainerNode, got %s", node);
+ return (ContainerNode) node;
+ }
+
+ private static MapNode extractMap(final Optional<? extends NormalizedNode<?, ?>> optNode) {
+ final NormalizedNode<?, ?> node = optNode.get();
+ verify(node instanceof MapNode, "Expected MapNode, got %s", node);
+ return (MapNode) node;
+ }
+
+ private static MapEntryNode extractMapEntry(final Optional<? extends NormalizedNode<?, ?>> optNode) {
+ final NormalizedNode<?, ?> node = optNode.get();
+ verify(node instanceof MapEntryNode, "Expected MapEntryNode, got %s", node);
+ return (MapEntryNode) node;
+ }
+
+ private static boolean isLongLivedStale(final ContainerNode attributes) {
+ return NormalizedNodes.findNode(attributes, ADJRIBIN_ATTRIBUTES_AID, LLGR_STALE_NID).isPresent();
+ }
+
+ private static boolean isLongLivedStaleTable(final Optional<NormalizedNode<?, ?>> optTable) {
+ final Optional<NormalizedNode<?, ?>> optAttributes = NormalizedNodes.findNode(optTable, ATTRIBUTES_NID);
+ return optAttributes.isPresent() ? isLongLivedStale(extractContainer(optAttributes)) : false;
+ }
+
+ private static ContainerNode effectiveAttributes(final Optional<? extends NormalizedNode<?, ?>> optUptodate) {
+ return optUptodate.map(leaf -> {
+ final Object value = leaf.getValue();
+ verify(value instanceof Boolean, "Expected boolean uptodate, got %s", value);
+ return ((Boolean) value).booleanValue() ? RIBNormalizedNodes.UPTODATE_ATTRIBUTES
+ : RIBNormalizedNodes.NOT_UPTODATE_ATTRIBUTES;
+ }).orElse(RIBNormalizedNodes.NOT_UPTODATE_ATTRIBUTES);
+ }