f97892a5afc28575f175f5f4807299e2c762498a
[bgpcep.git] / bgp / rib-impl / src / main / java / org / opendaylight / protocol / bgp / rib / impl / EffectiveRibInWriter.java
1 /*
2  * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.protocol.bgp.rib.impl;
9
10 import static com.google.common.base.Verify.verify;
11 import static com.google.common.base.Verify.verifyNotNull;
12 import static java.util.Objects.requireNonNull;
13 import static org.opendaylight.protocol.bgp.rib.spi.RIBNodeIdentifiers.ADJRIBIN_NID;
14 import static org.opendaylight.protocol.bgp.rib.spi.RIBNodeIdentifiers.ATTRIBUTES_NID;
15 import static org.opendaylight.protocol.bgp.rib.spi.RIBNodeIdentifiers.EFFRIBIN_NID;
16 import static org.opendaylight.protocol.bgp.rib.spi.RIBNodeIdentifiers.LLGR_STALE_NID;
17 import static org.opendaylight.protocol.bgp.rib.spi.RIBNodeIdentifiers.ROUTES_NID;
18 import static org.opendaylight.protocol.bgp.rib.spi.RIBNodeIdentifiers.TABLES_NID;
19 import static org.opendaylight.protocol.bgp.rib.spi.RIBNodeIdentifiers.UPTODATE_NID;
20
21 import com.google.common.collect.ImmutableList;
22 import com.google.common.collect.ImmutableMap;
23 import com.google.common.collect.ImmutableSet;
24 import com.google.common.util.concurrent.FluentFuture;
25 import com.google.common.util.concurrent.FutureCallback;
26 import com.google.common.util.concurrent.MoreExecutors;
27 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
28 import java.util.Collection;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Optional;
32 import java.util.Set;
33 import java.util.concurrent.ExecutionException;
34 import java.util.concurrent.atomic.LongAdder;
35 import org.checkerframework.checker.lock.qual.GuardedBy;
36 import org.checkerframework.checker.lock.qual.Holding;
37 import org.eclipse.jdt.annotation.NonNull;
38 import org.eclipse.jdt.annotation.Nullable;
39 import org.opendaylight.mdsal.common.api.CommitInfo;
40 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
41 import org.opendaylight.mdsal.dom.api.ClusteredDOMDataTreeChangeListener;
42 import org.opendaylight.mdsal.dom.api.DOMDataTreeChangeService;
43 import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
44 import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction;
45 import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
46 import org.opendaylight.protocol.bgp.openconfig.spi.BGPTableTypeRegistryConsumer;
47 import org.opendaylight.protocol.bgp.parser.impl.message.update.CommunityUtil;
48 import org.opendaylight.protocol.bgp.rib.impl.spi.RIB;
49 import org.opendaylight.protocol.bgp.rib.impl.spi.RIBSupportContext;
50 import org.opendaylight.protocol.bgp.rib.impl.spi.RIBSupportContextRegistry;
51 import org.opendaylight.protocol.bgp.rib.impl.spi.RibOutRefresh;
52 import org.opendaylight.protocol.bgp.rib.impl.state.peer.PrefixesInstalledCounters;
53 import org.opendaylight.protocol.bgp.rib.impl.state.peer.PrefixesReceivedCounters;
54 import org.opendaylight.protocol.bgp.rib.spi.RIBNormalizedNodes;
55 import org.opendaylight.protocol.bgp.rib.spi.RIBSupport;
56 import org.opendaylight.protocol.bgp.rib.spi.policy.BGPRibRoutingPolicy;
57 import org.opendaylight.protocol.bgp.rib.spi.policy.BGPRouteEntryImportParameters;
58 import org.opendaylight.protocol.bgp.route.targetcontrain.spi.ClientRouteTargetContrainCache;
59 import org.opendaylight.protocol.bgp.route.targetcontrain.spi.RouteTargetMembeshipUtil;
60 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev200120.path.attributes.Attributes;
61 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev200120.path.attributes.attributes.Communities;
62 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.PeerRole;
63 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.TablesKey;
64 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.route.target.constrain.rev180618.RouteTargetConstrainSubsequentAddressFamily;
65 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.route.target.constrain.rev180618.route.target.constrain.routes.route.target.constrain.routes.RouteTargetConstrainRoute;
66 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev200120.Ipv4AddressFamily;
67 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev200120.Ipv6AddressFamily;
68 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev200120.MplsLabeledVpnSubsequentAddressFamily;
69 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.types.rev200120.RouteTarget;
70 import org.opendaylight.yangtools.concepts.ListenerRegistration;
71 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
72 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
73 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
74 import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
75 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
76 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
77 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
78 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
79 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
80 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
81 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
82 import org.opendaylight.yangtools.yang.data.tree.api.DataTreeCandidate;
83 import org.opendaylight.yangtools.yang.data.tree.api.DataTreeCandidateNode;
84 import org.opendaylight.yangtools.yang.data.tree.api.ModificationType;
85 import org.slf4j.Logger;
86 import org.slf4j.LoggerFactory;
87
88 /**
89  * Implementation of the BGP import policy. Listens on peer's Adj-RIB-In, inspects all inbound
90  * routes in the context of the advertising peer's role and applies the inbound policy.
91  *
92  * <p>
93  * Inbound policy is applied as follows:
94  *
95  * <p>
96  * 1) if the peer is an eBGP peer, perform attribute replacement and filtering
97  * 2) check if a route is admissible based on attributes attached to it, as well as the
98  * advertising peer's role
99  * 3) output admitting routes with edited attributes into /bgp-rib/rib/peer/effective-rib-in/tables/routes
100  *
101  * <p>
102  * This class is NOT thread-safe.
103  */
104 final class EffectiveRibInWriter implements PrefixesReceivedCounters, PrefixesInstalledCounters,
105         AutoCloseable, ClusteredDOMDataTreeChangeListener {
106
107     private static final Logger LOG = LoggerFactory.getLogger(EffectiveRibInWriter.class);
108     private static final TablesKey IVP4_VPN_TABLE_KEY =
109         new TablesKey(Ipv4AddressFamily.VALUE, MplsLabeledVpnSubsequentAddressFamily.VALUE);
110     private static final TablesKey IVP6_VPN_TABLE_KEY =
111         new TablesKey(Ipv6AddressFamily.VALUE, MplsLabeledVpnSubsequentAddressFamily.VALUE);
112     private static final ImmutableList<Communities> STALE_LLGR_COMMUNUTIES =
113         ImmutableList.of(StaleCommunities.STALE_LLGR);
114     private static final Attributes STALE_LLGR_ATTRIBUTES = new org.opendaylight.yang.gen.v1.urn.opendaylight.params
115             .xml.ns.yang.bgp.message.rev200120.path.attributes.AttributesBuilder()
116             .setCommunities(STALE_LLGR_COMMUNUTIES)
117             .build();
118     private static final ChoiceNode EMPTY_ROUTES = Builders.choiceBuilder().withNodeIdentifier(ROUTES_NID).build();
119
120     private final RIBSupportContextRegistry registry;
121     private final YangInstanceIdentifier peerIId;
122     private final YangInstanceIdentifier effRibTables;
123     private final DOMDataTreeChangeService service;
124     private final List<RouteTarget> rtMemberships;
125     private final RibOutRefresh vpnTableRefresher;
126     private final ClientRouteTargetContrainCache rtCache;
127     private ListenerRegistration<?> reg;
128     private DOMTransactionChain chain;
129     private final Map<TablesKey, LongAdder> prefixesReceived;
130     private final Map<TablesKey, LongAdder> prefixesInstalled;
131     private final BGPRibRoutingPolicy ribPolicies;
132     private final BGPRouteEntryImportParameters peerImportParameters;
133     private final BGPTableTypeRegistryConsumer tableTypeRegistry;
134     @GuardedBy("this")
135     private FluentFuture<? extends CommitInfo> submitted;
136     private boolean rtMembershipsUpdated;
137
138     EffectiveRibInWriter(
139             final BGPRouteEntryImportParameters peer,
140             final RIB rib,
141             final DOMTransactionChain chain,
142             final YangInstanceIdentifier peerIId,
143             final Set<TablesKey> tables,
144             final BGPTableTypeRegistryConsumer tableTypeRegistry,
145             final List<RouteTarget> rtMemberships,
146             final ClientRouteTargetContrainCache rtCache) {
147         registry = requireNonNull(rib.getRibSupportContext());
148         this.chain = requireNonNull(chain);
149         this.peerIId = requireNonNull(peerIId);
150         effRibTables = this.peerIId.node(EFFRIBIN_NID);
151         prefixesInstalled = buildPrefixesTables(tables);
152         prefixesReceived = buildPrefixesTables(tables);
153         ribPolicies = requireNonNull(rib.getRibPolicies());
154         service = requireNonNull(rib.getService());
155         this.tableTypeRegistry = requireNonNull(tableTypeRegistry);
156         peerImportParameters = peer;
157         this.rtMemberships = rtMemberships;
158         this.rtCache = rtCache;
159         vpnTableRefresher = rib;
160     }
161
162     public void init() {
163         final DOMDataTreeIdentifier treeId = new DOMDataTreeIdentifier(LogicalDatastoreType.OPERATIONAL,
164             peerIId.node(ADJRIBIN_NID).node(TABLES_NID));
165         LOG.debug("Registered Effective RIB on {}", peerIId);
166         reg = requireNonNull(service).registerDataTreeChangeListener(treeId, this);
167     }
168
169     private static Map<TablesKey, LongAdder> buildPrefixesTables(final Set<TablesKey> tables) {
170         final ImmutableMap.Builder<TablesKey, LongAdder> b = ImmutableMap.builder();
171         tables.forEach(table -> b.put(table, new LongAdder()));
172         return b.build();
173     }
174
175     @Override
176     public synchronized void onInitialData() {
177         // FIXME: update as if root was deleted
178     }
179
180     @Override
181     public synchronized void onDataTreeChanged(final List<DataTreeCandidate> changes) {
182         if (chain == null) {
183             LOG.trace("Chain closed. Ignoring Changes : {}", changes);
184             return;
185         }
186
187         LOG.trace("Data changed called to effective RIB. Change : {}", changes);
188         DOMDataTreeWriteTransaction tx = null;
189         for (final DataTreeCandidate tc : changes) {
190             final YangInstanceIdentifier rootPath = tc.getRootPath();
191             final DataTreeCandidateNode root = tc.getRootNode();
192             for (final DataTreeCandidateNode table : root.childNodes()) {
193                 if (tx == null) {
194                     tx = chain.newWriteOnlyTransaction();
195                 }
196                 changeDataTree(tx, rootPath, root, table);
197             }
198         }
199
200         if (tx != null) {
201             final FluentFuture<? extends CommitInfo> future = tx.commit();
202             submitted = future;
203             future.addCallback(new FutureCallback<CommitInfo>() {
204                 @Override
205                 public void onSuccess(final CommitInfo result) {
206                     LOG.trace("Successful commit");
207                 }
208
209                 @Override
210                 public void onFailure(final Throwable trw) {
211                     LOG.error("Failed commit", trw);
212                 }
213             }, MoreExecutors.directExecutor());
214         }
215
216         //Refresh VPN Table if RT Memberships were updated
217         if (rtMembershipsUpdated) {
218             vpnTableRefresher.refreshTable(IVP4_VPN_TABLE_KEY, peerImportParameters.getFromPeerId());
219             vpnTableRefresher.refreshTable(IVP6_VPN_TABLE_KEY, peerImportParameters.getFromPeerId());
220             rtMembershipsUpdated = false;
221         }
222     }
223
224     @Override
225     public synchronized void close() {
226         if (reg != null) {
227             reg.close();
228             reg = null;
229         }
230         if (submitted != null) {
231             try {
232                 submitted.get();
233             } catch (final InterruptedException | ExecutionException throwable) {
234                 LOG.error("Write routes failed", throwable);
235             }
236         }
237         if (chain != null) {
238             chain.close();
239             chain = null;
240         }
241         prefixesReceived.values().forEach(LongAdder::reset);
242         prefixesInstalled.values().forEach(LongAdder::reset);
243     }
244
245     @Override
246     public long getPrefixedReceivedCount(final TablesKey tablesKey) {
247         final LongAdder counter = prefixesReceived.get(tablesKey);
248         if (counter == null) {
249             return 0;
250         }
251         return counter.longValue();
252     }
253
254     @Override
255     public Set<TablesKey> getTableKeys() {
256         return ImmutableSet.copyOf(prefixesReceived.keySet());
257     }
258
259     @Override
260     public boolean isSupported(final TablesKey tablesKey) {
261         return prefixesReceived.containsKey(tablesKey);
262     }
263
264     @Override
265     public long getPrefixedInstalledCount(final TablesKey tablesKey) {
266         final LongAdder counter = prefixesInstalled.get(tablesKey);
267         if (counter == null) {
268             return 0;
269         }
270         return counter.longValue();
271     }
272
273     @Override
274     public long getTotalPrefixesInstalled() {
275         return prefixesInstalled.values().stream().mapToLong(LongAdder::longValue).sum();
276     }
277
278     @Holding("this")
279     private void changeDataTree(final DOMDataTreeWriteTransaction tx, final YangInstanceIdentifier rootPath,
280             final DataTreeCandidateNode root, final DataTreeCandidateNode table) {
281         final PathArgument lastArg = table.name();
282         verify(lastArg instanceof NodeIdentifierWithPredicates, "Unexpected type %s in path %s", lastArg.getClass(),
283             rootPath);
284         final NodeIdentifierWithPredicates tableKey = (NodeIdentifierWithPredicates) lastArg;
285         final RIBSupportContext ribContext = registry.getRIBSupportContext(tableKey);
286         if (ribContext == null) {
287             LOG.warn("Table {} is not supported, ignoring event", tableKey);
288             return;
289         }
290
291         final YangInstanceIdentifier effectiveTablePath = effectiveTablePath(tableKey);
292         final ModificationType modificationType = root.modificationType();
293         LOG.debug("Effective table {} modification type {}", effectiveTablePath, modificationType);
294         switch (modificationType) {
295             case DISAPPEARED:
296             case DELETE:
297                 deleteTable(tx, ribContext, effectiveTablePath, table);
298                 break;
299             case APPEARED:
300             case WRITE:
301                 writeTable(tx, ribContext, effectiveTablePath, table);
302                 break;
303             case SUBTREE_MODIFIED:
304                 modifyTable(tx, ribContext, effectiveTablePath, table);
305                 break;
306             case UNMODIFIED:
307                 LOG.info("Ignoring spurious notification on {} data {}", rootPath, table);
308                 break;
309             default:
310                 LOG.warn("Ignoring unhandled root {}", table);
311                 break;
312         }
313     }
314
315     private void deleteTable(final DOMDataTreeWriteTransaction tx, final RIBSupportContext ribContext,
316             final YangInstanceIdentifier effectiveTablePath, final DataTreeCandidateNode table) {
317         LOG.debug("Delete Effective Table {}", effectiveTablePath);
318         onDeleteTable(ribContext.getRibSupport(), effectiveTablePath, table.dataBefore());
319         tx.delete(LogicalDatastoreType.OPERATIONAL, effectiveTablePath);
320     }
321
322     private void modifyTable(final DOMDataTreeWriteTransaction tx, final RIBSupportContext ribContext,
323             final YangInstanceIdentifier effectiveTablePath, final DataTreeCandidateNode table) {
324         LOG.debug("Modify Effective Table {}", effectiveTablePath);
325
326         final boolean wasLongLivedStale = isLongLivedStaleTable(table.findDataBefore());
327         final boolean longLivedStale = isLongLivedStaleTable(table.findDataAfter());
328         if (wasLongLivedStale != longLivedStale) {
329             LOG.debug("LLGR_STALE flag flipped {}, overwriting table {}", longLivedStale ? "ON" : "OFF",
330                     effectiveTablePath);
331             writeTable(tx, ribContext, effectiveTablePath, table);
332             return;
333         }
334
335         final var modifiedAttrs = table.modifiedChild(ATTRIBUTES_NID);
336         if (modifiedAttrs != null) {
337             final YangInstanceIdentifier effAttrsPath = effectiveTablePath.node(ATTRIBUTES_NID);
338             final var attrsAfter = modifiedAttrs.dataAfter();
339             if (attrsAfter != null) {
340                 tx.put(LogicalDatastoreType.OPERATIONAL, effAttrsPath,
341                     effectiveAttributes(extractContainer(attrsAfter)));
342             } else {
343                 tx.delete(LogicalDatastoreType.OPERATIONAL, effAttrsPath);
344             }
345         }
346
347         final var modifiedRoutes = table.modifiedChild(ROUTES_NID);
348         if (modifiedRoutes != null) {
349             final RIBSupport<?, ?> ribSupport = ribContext.getRibSupport();
350             switch (modifiedRoutes.modificationType()) {
351                 case APPEARED:
352                 case WRITE:
353                     deleteRoutesBefore(tx, ribSupport, effectiveTablePath, modifiedRoutes);
354                     // XXX: YANG Tools seems to have an issue stacking DELETE with child WRITE
355                     tx.put(LogicalDatastoreType.OPERATIONAL, effectiveTablePath.node(ROUTES_NID), EMPTY_ROUTES);
356                     writeRoutesAfter(tx, ribSupport, effectiveTablePath, modifiedRoutes.findDataAfter(),
357                         longLivedStale);
358                     break;
359                 case DELETE:
360                 case DISAPPEARED:
361                     deleteRoutesBefore(tx, ribSupport, effectiveTablePath, modifiedRoutes);
362                     tx.delete(LogicalDatastoreType.OPERATIONAL, effectiveTablePath.node(ROUTES_NID));
363                     break;
364                 case SUBTREE_MODIFIED:
365                     for (DataTreeCandidateNode modifiedRoute : ribSupport.changedRoutes(modifiedRoutes)) {
366                         processRoute(tx, ribSupport, effectiveTablePath, modifiedRoute, longLivedStale);
367                     }
368                     break;
369                 case UNMODIFIED:
370                     // No-op
371                     return;
372                 default:
373                     LOG.warn("Ignoring modified routes {}", modifiedRoutes);
374                     break;
375             }
376         }
377     }
378
379     private void writeTable(final DOMDataTreeWriteTransaction tx, final RIBSupportContext ribContext,
380             final YangInstanceIdentifier effectiveTablePath, final DataTreeCandidateNode table) {
381         LOG.debug("Write Effective Table {}", effectiveTablePath);
382         onDeleteTable(ribContext.getRibSupport(), effectiveTablePath, table.dataBefore());
383
384         final var node = table.dataAfter();
385         if (node != null) {
386             verify(node instanceof MapEntryNode, "Expected MapEntryNode, got %s", node);
387             final MapEntryNode tableAfter = (MapEntryNode) node;
388             ribContext.createEmptyTableStructure(tx, effectiveTablePath);
389
390             final DataContainerChild maybeAttrsAfter = tableAfter.childByArg(ATTRIBUTES_NID);
391             final boolean longLivedStale;
392             if (maybeAttrsAfter != null) {
393                 final ContainerNode attrsAfter = extractContainer(maybeAttrsAfter);
394                 longLivedStale = isLongLivedStale(attrsAfter);
395                 tx.put(LogicalDatastoreType.OPERATIONAL, effectiveTablePath.node(ATTRIBUTES_NID),
396                     effectiveAttributes(attrsAfter));
397             } else {
398                 longLivedStale = false;
399             }
400
401             writeRoutesAfter(tx, ribContext.getRibSupport(), effectiveTablePath,
402                 NormalizedNodes.findNode(tableAfter, ROUTES_NID), longLivedStale);
403         }
404     }
405
406     // Performs house-keeping when the contents of a table is deleted
407     private void onDeleteTable(final RIBSupport<?, ?> ribSupport, final YangInstanceIdentifier effectiveTablePath,
408             final @Nullable NormalizedNode tableBefore) {
409         // Routes are special in that we need to process the to keep our counters accurate
410         final var maybeRoutesBefore = findRoutesMap(ribSupport, NormalizedNodes.findNode(tableBefore, ROUTES_NID));
411         if (maybeRoutesBefore.isPresent()) {
412             onRoutesDeleted(ribSupport, effectiveTablePath, extractMap(maybeRoutesBefore).body());
413         }
414     }
415
416     private void deleteRoutesBefore(final DOMDataTreeWriteTransaction tx, final RIBSupport<?, ?> ribSupport,
417             final YangInstanceIdentifier effectiveTablePath, final DataTreeCandidateNode modifiedRoutes) {
418         final Optional<NormalizedNode> maybeRoutesBefore =
419             NormalizedNodes.findNode(modifiedRoutes.dataBefore(), ribSupport.relativeRoutesPath());
420         if (maybeRoutesBefore.isPresent()) {
421             onRoutesDeleted(ribSupport, effectiveTablePath, extractMap(maybeRoutesBefore).body());
422         }
423     }
424
425     private void writeRoutesAfter(final DOMDataTreeWriteTransaction tx, final RIBSupport<?, ?> ribSupport,
426             final YangInstanceIdentifier effectiveTablePath, final Optional<NormalizedNode> routesAfter,
427             final boolean longLivedStale) {
428         final Optional<NormalizedNode> maybeRoutesAfter = NormalizedNodes.findNode(routesAfter,
429             ribSupport.relativeRoutesPath());
430         if (maybeRoutesAfter.isPresent()) {
431             final YangInstanceIdentifier routesPath = routeMapPath(ribSupport, effectiveTablePath);
432             for (MapEntryNode routeAfter : extractMap(maybeRoutesAfter).body()) {
433                 writeRoute(tx, ribSupport, routesPath.node(routeAfter.name()), null, routeAfter, longLivedStale);
434             }
435         }
436     }
437
438     private void onRoutesDeleted(final RIBSupport<?, ?> ribSupport, final YangInstanceIdentifier effectiveTablePath,
439             final Collection<MapEntryNode> deletedRoutes) {
440         if (RouteTargetConstrainSubsequentAddressFamily.VALUE.equals(ribSupport.getTablesKey().getSafi())) {
441             final YangInstanceIdentifier routesPath = routeMapPath(ribSupport, effectiveTablePath);
442             for (final MapEntryNode routeBefore : deletedRoutes) {
443                 deleteRouteTarget(ribSupport, routesPath.node(routeBefore.name()), routeBefore);
444             }
445             rtMembershipsUpdated = true;
446         }
447
448         final TablesKey tablesKey = ribSupport.getTablesKey();
449         CountersUtil.add(prefixesInstalled.get(tablesKey), tablesKey, -deletedRoutes.size());
450     }
451
452     private void processRoute(final DOMDataTreeWriteTransaction tx, final RIBSupport<?, ?> ribSupport,
453             final YangInstanceIdentifier routesPath, final DataTreeCandidateNode route, final boolean longLivedStale) {
454         LOG.debug("Process route {}", route.name());
455         final YangInstanceIdentifier routePath = ribSupport.routePath(routesPath, route.name());
456         switch (route.modificationType()) {
457             case DELETE:
458             case DISAPPEARED:
459                 deleteRoute(tx, ribSupport, routePath, route.dataBefore());
460                 break;
461             case UNMODIFIED:
462                 // No-op
463                 break;
464             case APPEARED:
465             case SUBTREE_MODIFIED:
466             case WRITE:
467                 writeRoute(tx, ribSupport, routePath, route.dataBefore(), route.getDataAfter(), longLivedStale);
468                 break;
469             default:
470                 LOG.warn("Ignoring unhandled route {}", route);
471                 break;
472         }
473     }
474
475     private void deleteRoute(final DOMDataTreeWriteTransaction tx, final RIBSupport<?, ?> ribSupport,
476             final YangInstanceIdentifier routeIdPath, final NormalizedNode route) {
477         handleRouteTarget(ModificationType.DELETE, ribSupport, routeIdPath, route);
478         tx.delete(LogicalDatastoreType.OPERATIONAL, routeIdPath);
479         LOG.debug("Route deleted. routeId={}", routeIdPath);
480         final TablesKey tablesKey = ribSupport.getTablesKey();
481         CountersUtil.decrement(prefixesInstalled.get(tablesKey), tablesKey);
482     }
483
484     private void writeRoute(final DOMDataTreeWriteTransaction tx, final RIBSupport<?, ?> ribSupport,
485             final YangInstanceIdentifier routePath, final @Nullable NormalizedNode routeBefore,
486             final @NonNull NormalizedNode routeAfter, final boolean longLivedStale) {
487         final TablesKey tablesKey = ribSupport.getTablesKey();
488         CountersUtil.increment(prefixesReceived.get(tablesKey), tablesKey);
489         // Lookup per-table attributes from RIBSupport
490         final ContainerNode advertisedAttrs = (ContainerNode) NormalizedNodes.findNode(routeAfter,
491             ribSupport.routeAttributesIdentifier()).orElse(null);
492         final Attributes routeAttrs = ribSupport.attributeFromContainerNode(advertisedAttrs);
493         final Optional<Attributes> optEffAtt;
494         // In case we want to add LLGR_STALE we do not process route through policies since it may be
495         // considered as received with LLGR_STALE from peer which is not true.
496         if (longLivedStale) {
497             // LLGR procedures are in effect. If the route is tagged with NO_LLGR, it needs to be removed.
498             final List<Communities> effCommunities = routeAttrs.getCommunities();
499             if (effCommunities != null && effCommunities.contains(CommunityUtil.NO_LLGR)) {
500                 deleteRoute(tx, ribSupport, routePath, routeBefore);
501                 return;
502             }
503             optEffAtt = Optional.of(wrapLongLivedStale(routeAttrs));
504         } else {
505             optEffAtt = ribPolicies.applyImportPolicies(peerImportParameters, routeAttrs,
506                 verifyNotNull(tableTypeRegistry.getAfiSafiType(ribSupport.getTablesKey())));
507         }
508         if (optEffAtt.isEmpty()) {
509             deleteRoute(tx, ribSupport, routePath, routeBefore);
510             return;
511         }
512         handleRouteTarget(ModificationType.WRITE, ribSupport, routePath, routeAfter);
513         tx.put(LogicalDatastoreType.OPERATIONAL, routePath, routeAfter);
514         CountersUtil.increment(prefixesInstalled.get(tablesKey), tablesKey);
515
516         final Attributes attToStore = optEffAtt.orElseThrow();
517         if (!attToStore.equals(routeAttrs)) {
518             final YangInstanceIdentifier attPath = routePath.node(ribSupport.routeAttributesIdentifier());
519             final ContainerNode finalAttribute = ribSupport.attributeToContainerNode(attPath, attToStore);
520             tx.put(LogicalDatastoreType.OPERATIONAL, attPath, finalAttribute);
521         }
522     }
523
524     private void addRouteTarget(final RouteTargetConstrainRoute rtc) {
525         final RouteTarget rtMembership = RouteTargetMembeshipUtil.getRT(rtc);
526         if (PeerRole.Ebgp != peerImportParameters.getFromPeerRole()) {
527             rtCache.cacheRoute(rtc);
528         }
529         rtMemberships.add(rtMembership);
530     }
531
532     private void deleteRouteTarget(final RIBSupport<?, ?> ribSupport, final YangInstanceIdentifier routeIdPath,
533             final NormalizedNode route) {
534         deleteRouteTarget((RouteTargetConstrainRoute) ribSupport.fromNormalizedNode(routeIdPath, route));
535     }
536
537     private void deleteRouteTarget(final RouteTargetConstrainRoute rtc) {
538         final RouteTarget rtMembership = RouteTargetMembeshipUtil.getRT(rtc);
539         if (PeerRole.Ebgp != peerImportParameters.getFromPeerRole()) {
540             rtCache.uncacheRoute(rtc);
541         }
542         rtMemberships.remove(rtMembership);
543     }
544
545     private void handleRouteTarget(final ModificationType modificationType, final RIBSupport<?, ?> ribSupport,
546             final YangInstanceIdentifier routeIdPath, final NormalizedNode route) {
547         if (RouteTargetConstrainSubsequentAddressFamily.VALUE.equals(ribSupport.getTablesKey().getSafi())) {
548             final var rtc = (RouteTargetConstrainRoute) ribSupport.fromNormalizedNode(routeIdPath, route);
549             if (ModificationType.DELETE == modificationType) {
550                 deleteRouteTarget(rtc);
551             } else {
552                 addRouteTarget(rtc);
553             }
554             rtMembershipsUpdated = true;
555         }
556     }
557
558     @SuppressFBWarnings("UPM_UNCALLED_PRIVATE_METHOD")
559     private static Attributes wrapLongLivedStale(final Attributes attrs) {
560         if (attrs == null) {
561             return STALE_LLGR_ATTRIBUTES;
562         }
563
564         final List<Communities> oldCommunities = attrs.getCommunities();
565         final List<Communities> newCommunities;
566         if (oldCommunities != null) {
567             if (oldCommunities.contains(StaleCommunities.STALE_LLGR)) {
568                 return attrs;
569             }
570             newCommunities = StaleCommunities.create(oldCommunities);
571         } else {
572             newCommunities = STALE_LLGR_COMMUNUTIES;
573         }
574
575         return new org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev200120
576                 .path.attributes.AttributesBuilder(attrs).setCommunities(newCommunities).build();
577     }
578
579     // XXX: this should be moved to YangInstanceIdentifier at some point
580     private static YangInstanceIdentifier concat(final YangInstanceIdentifier parent, final List<PathArgument> args) {
581         YangInstanceIdentifier ret = parent;
582         for (PathArgument arg : args) {
583             ret = ret.node(arg);
584         }
585         return ret;
586     }
587
588     private YangInstanceIdentifier effectiveTablePath(final NodeIdentifierWithPredicates tableKey) {
589         return effRibTables.node(TABLES_NID).node(tableKey);
590     }
591
592     private static YangInstanceIdentifier routeMapPath(final RIBSupport<?, ?> ribSupport,
593             final YangInstanceIdentifier tablePath) {
594         return concat(tablePath.node(ROUTES_NID), ribSupport.relativeRoutesPath());
595     }
596
597     private static Optional<NormalizedNode> findRoutesMap(final RIBSupport<?, ?> ribSupport,
598             final Optional<NormalizedNode> optRoutes) {
599         return NormalizedNodes.findNode(optRoutes, ribSupport.relativeRoutesPath());
600     }
601
602     private static ContainerNode extractContainer(final NormalizedNode node) {
603         verify(node instanceof ContainerNode, "Expected ContainerNode, got %s", node);
604         return (ContainerNode) node;
605     }
606
607     private static MapNode extractMap(final Optional<? extends NormalizedNode> optNode) {
608         final NormalizedNode node = optNode.orElseThrow();
609         verify(node instanceof MapNode, "Expected MapNode, got %s", node);
610         return (MapNode) node;
611     }
612
613     private static boolean isLongLivedStale(final ContainerNode attributes) {
614         return NormalizedNodes.findNode(attributes, LLGR_STALE_NID).isPresent();
615     }
616
617     private static boolean isLongLivedStaleTable(final Optional<NormalizedNode> optTable) {
618         final Optional<NormalizedNode> optAttributes = NormalizedNodes.findNode(optTable, ATTRIBUTES_NID);
619         return optAttributes.isPresent() && isLongLivedStale(extractContainer(optAttributes.orElseThrow()));
620     }
621
622     private static ContainerNode effectiveAttributes(final ContainerNode attrs) {
623         final var upToDate = attrs.childByArg(UPTODATE_NID);
624         if (upToDate != null) {
625             final Object value = upToDate.body();
626             verify(value instanceof Boolean, "Expected boolean uptodate, got %s", value);
627             if ((Boolean) value) {
628                 return RIBNormalizedNodes.UPTODATE_ATTRIBUTES;
629             }
630         }
631         return RIBNormalizedNodes.NOT_UPTODATE_ATTRIBUTES;
632     }
633 }