0e217ec6dab37b46cb7a4549c2acccae358fd102
[bgpcep.git] / bgp / rib-impl / src / main / java / org / opendaylight / protocol / bgp / rib / impl / LocRibWriter.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 java.util.Objects.requireNonNull;
12 import static org.opendaylight.protocol.bgp.rib.spi.RIBNodeIdentifiers.ATTRIBUTES_NID;
13 import static org.opendaylight.protocol.bgp.rib.spi.RIBNodeIdentifiers.EFFRIBIN_NID;
14 import static org.opendaylight.protocol.bgp.rib.spi.RIBNodeIdentifiers.LOCRIB_NID;
15 import static org.opendaylight.protocol.bgp.rib.spi.RIBNodeIdentifiers.PEER_NID;
16 import static org.opendaylight.protocol.bgp.rib.spi.RIBNodeIdentifiers.ROUTES_NID;
17 import static org.opendaylight.protocol.bgp.rib.spi.RIBNodeIdentifiers.TABLES_NID;
18 import static org.opendaylight.protocol.bgp.rib.spi.RIBNodeIdentifiers.UPTODATE_NID;
19
20 import com.google.common.util.concurrent.FutureCallback;
21 import com.google.common.util.concurrent.MoreExecutors;
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Map.Entry;
28 import java.util.Set;
29 import java.util.concurrent.atomic.LongAdder;
30 import org.checkerframework.checker.lock.qual.GuardedBy;
31 import org.eclipse.jdt.annotation.NonNull;
32 import org.opendaylight.mdsal.common.api.CommitInfo;
33 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
34 import org.opendaylight.mdsal.dom.api.ClusteredDOMDataTreeChangeListener;
35 import org.opendaylight.mdsal.dom.api.DOMDataTreeChangeService;
36 import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
37 import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteOperations;
38 import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction;
39 import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
40 import org.opendaylight.protocol.bgp.mode.api.PathSelectionMode;
41 import org.opendaylight.protocol.bgp.mode.api.RouteEntry;
42 import org.opendaylight.protocol.bgp.rib.impl.spi.RibOutRefresh;
43 import org.opendaylight.protocol.bgp.rib.impl.state.rib.TotalPathsCounter;
44 import org.opendaylight.protocol.bgp.rib.impl.state.rib.TotalPrefixesCounter;
45 import org.opendaylight.protocol.bgp.rib.spi.BGPPeerTracker;
46 import org.opendaylight.protocol.bgp.rib.spi.IdentifierUtils;
47 import org.opendaylight.protocol.bgp.rib.spi.RIBNormalizedNodes;
48 import org.opendaylight.protocol.bgp.rib.spi.RIBSupport;
49 import org.opendaylight.protocol.bgp.rib.spi.RouterId;
50 import org.opendaylight.protocol.bgp.rib.spi.entry.ActualBestPathRoutes;
51 import org.opendaylight.protocol.bgp.rib.spi.entry.AdvertizedRoute;
52 import org.opendaylight.protocol.bgp.rib.spi.entry.StaleBestPathRoute;
53 import org.opendaylight.protocol.bgp.rib.spi.policy.BGPRibRoutingPolicy;
54 import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.bgp.types.rev151009.AfiSafiType;
55 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.AsNumber;
56 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.PeerId;
57 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.Tables;
58 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.TablesKey;
59 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.tables.Routes;
60 import org.opendaylight.yangtools.concepts.ListenerRegistration;
61 import org.opendaylight.yangtools.yang.binding.ChildOf;
62 import org.opendaylight.yangtools.yang.binding.ChoiceIn;
63 import org.opendaylight.yangtools.yang.binding.DataObject;
64 import org.opendaylight.yangtools.yang.common.Uint32;
65 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
66 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
67 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
68 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
69 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
70 import org.opendaylight.yangtools.yang.data.tree.api.DataTreeCandidate;
71 import org.opendaylight.yangtools.yang.data.tree.api.DataTreeCandidateNode;
72 import org.slf4j.Logger;
73 import org.slf4j.LoggerFactory;
74
75 // This class is NOT thread-safe
76 final class LocRibWriter<C extends Routes & DataObject & ChoiceIn<Tables>, S extends ChildOf<? super C>>
77         implements AutoCloseable, RibOutRefresh, TotalPrefixesCounter, TotalPathsCounter,
78         ClusteredDOMDataTreeChangeListener {
79
80     private static final Logger LOG = LoggerFactory.getLogger(LocRibWriter.class);
81
82     private final Map<String, RouteEntry<C, S>> routeEntries = new HashMap<>();
83     private final long ourAs;
84     private final RIBSupport<C, S> ribSupport;
85     private final DOMDataTreeChangeService dataBroker;
86     private final PathSelectionMode pathSelectionMode;
87     private final LongAdder totalPathsCounter = new LongAdder();
88     private final LongAdder totalPrefixesCounter = new LongAdder();
89     private final RouteEntryDependenciesContainerImpl entryDep;
90     private final BGPPeerTracker peerTracker;
91     private final YangInstanceIdentifier ribIId;
92     private final YangInstanceIdentifier locRibTableIID;
93
94     private DOMTransactionChain chain;
95     @GuardedBy("this")
96     private ListenerRegistration<?> reg;
97
98     private LocRibWriter(final RIBSupport<C, S> ribSupport,
99             final DOMTransactionChain chain,
100             final YangInstanceIdentifier ribIId,
101             final Uint32 ourAs,
102             final DOMDataTreeChangeService dataBroker,
103             final BGPRibRoutingPolicy ribPolicies,
104             final BGPPeerTracker peerTracker,
105             final AfiSafiType afiSafiType,
106             final PathSelectionMode pathSelectionMode) {
107         this.chain = requireNonNull(chain);
108         this.ribIId = requireNonNull(ribIId);
109         this.ribSupport = requireNonNull(ribSupport);
110
111         locRibTableIID = ribIId.node(LOCRIB_NID).node(TABLES_NID).node(ribSupport.emptyTable().name()).toOptimized();
112
113         this.ourAs = ourAs.toJava();
114         this.dataBroker = requireNonNull(dataBroker);
115         this.peerTracker = peerTracker;
116         this.pathSelectionMode = pathSelectionMode;
117
118         entryDep = new RouteEntryDependenciesContainerImpl(this.ribSupport, this.peerTracker, ribPolicies,
119                 afiSafiType, locRibTableIID);
120     }
121
122     public static <C extends Routes & DataObject & ChoiceIn<Tables>, S extends ChildOf<? super C>>
123                 LocRibWriter<C, S> create(
124             final @NonNull RIBSupport<C, S> ribSupport,
125             final @NonNull AfiSafiType afiSafiType,
126             final @NonNull DOMTransactionChain chain,
127             final @NonNull YangInstanceIdentifier ribIId,
128             final @NonNull AsNumber ourAs,
129             final @NonNull DOMDataTreeChangeService dataBroker,
130             final BGPRibRoutingPolicy ribPolicies,
131             final @NonNull BGPPeerTracker peerTracker,
132             final @NonNull PathSelectionMode pathSelectionStrategy) {
133         final LocRibWriter<C, S> ret = new LocRibWriter<>(ribSupport, chain, ribIId, ourAs.getValue(), dataBroker,
134             ribPolicies, peerTracker, afiSafiType, pathSelectionStrategy);
135         ret.init();
136         return ret;
137     }
138
139     private synchronized void init() {
140         final DOMDataTreeWriteTransaction tx = chain.newWriteOnlyTransaction();
141         tx.put(LogicalDatastoreType.OPERATIONAL, locRibTableIID.node(ATTRIBUTES_NID).node(UPTODATE_NID),
142                 RIBNormalizedNodes.ATTRIBUTES_UPTODATE_TRUE);
143         tx.commit().addCallback(new FutureCallback<CommitInfo>() {
144             @Override
145             public void onSuccess(final CommitInfo result) {
146                 LOG.trace("Successful commit");
147             }
148
149             @Override
150             public void onFailure(final Throwable trw) {
151                 LOG.error("Failed commit", trw);
152             }
153         }, MoreExecutors.directExecutor());
154
155         reg = dataBroker.registerDataTreeChangeListener(new DOMDataTreeIdentifier(
156             LogicalDatastoreType.OPERATIONAL, ribIId.node(PEER_NID).node(PEER_NID).node(EFFRIBIN_NID).node(TABLES_NID)
157                 .node(locRibTableIID.getLastPathArgument())), this);
158     }
159
160     /**
161      * Re-initialize this LocRibWriter with new transaction chain.
162      *
163      * @param newChain new transaction chain
164      */
165     synchronized void restart(final @NonNull DOMTransactionChain newChain) {
166         requireNonNull(newChain);
167         close();
168         chain = newChain;
169         init();
170     }
171
172     @Override
173     public synchronized void close() {
174         if (reg != null) {
175             reg.close();
176             reg = null;
177         }
178         if (chain != null) {
179             chain.close();
180             chain = null;
181         }
182     }
183
184     private @NonNull RouteEntry<C, S> createEntry(final String routeId) {
185         final RouteEntry<C, S> ret = pathSelectionMode.createRouteEntry();
186         routeEntries.put(routeId, ret);
187         totalPrefixesCounter.increment();
188         LOG.trace("Created new entry for {}", routeId);
189         return ret;
190     }
191
192     @Override
193     public synchronized void onInitialData() {
194         // FIXME: we need to do something
195     }
196
197     /**
198      * We use two-stage processing here in hopes that we avoid duplicate
199      * calculations when multiple peers have changed a particular entry.
200      *
201      * @param changes on supported table
202      */
203     @Override
204     @SuppressWarnings("checkstyle:illegalCatch")
205     public synchronized void onDataTreeChanged(final List<DataTreeCandidate> changes) {
206         if (chain == null) {
207             LOG.trace("Chain closed, ignoring received data change {} to LocRib {}", changes, this);
208             return;
209         }
210         LOG.trace("Received data change {} to LocRib {}", changes, this);
211         final DOMDataTreeWriteTransaction tx = chain.newWriteOnlyTransaction();
212         try {
213             final Map<RouteUpdateKey, RouteEntry<C, S>> toUpdate = update(tx, changes);
214
215             if (!toUpdate.isEmpty()) {
216                 walkThrough(tx, toUpdate.entrySet());
217             }
218         } catch (final Exception e) {
219             LOG.error("Failed to completely propagate updates {}, state is undefined", changes, e);
220         } finally {
221             tx.commit().addCallback(new FutureCallback<CommitInfo>() {
222                 @Override
223                 public void onSuccess(final CommitInfo result) {
224                     LOG.trace("Successful commit");
225                 }
226
227                 @Override
228                 public void onFailure(final Throwable trw) {
229                     LOG.error("Failed commit", trw);
230                 }
231             }, MoreExecutors.directExecutor());
232         }
233     }
234
235     private Map<RouteUpdateKey, RouteEntry<C, S>> update(final DOMDataTreeWriteOperations tx,
236             final Collection<DataTreeCandidate> changes) {
237         final Map<RouteUpdateKey, RouteEntry<C, S>> ret = new HashMap<>();
238         for (final DataTreeCandidate tc : changes) {
239             final DataTreeCandidateNode table = tc.getRootNode();
240             final RouterId peerUuid = RouterId.forPeerId(IdentifierUtils.peerKeyToPeerId(tc.getRootPath()));
241
242             // Initialize Peer with routes under loc rib
243             if (!routeEntries.isEmpty() && table.dataBefore() == null) {
244                 final org.opendaylight.protocol.bgp.rib.spi.Peer toPeer
245                         = peerTracker.getPeer(peerUuid.getPeerId());
246                 if (toPeer != null && toPeer.supportsTable(entryDep.getLocalTablesKey())) {
247                     LOG.debug("Peer {} table has been created, inserting existent routes", toPeer.getPeerId());
248                     final List<ActualBestPathRoutes<C, S>> routesToStore = new ArrayList<>();
249                     for (final Entry<String, RouteEntry<C, S>> entry : routeEntries.entrySet()) {
250                         final List<ActualBestPathRoutes<C, S>> filteredRoute = entry.getValue()
251                                 .actualBestPaths(ribSupport, new RouteEntryInfoImpl(toPeer, entry.getKey()));
252                         routesToStore.addAll(filteredRoute);
253                     }
254                     toPeer.initializeRibOut(entryDep, routesToStore);
255                 }
256             }
257             // Process new routes from Peer
258             updateNodes(table, peerUuid, tx, ret);
259         }
260         return ret;
261     }
262
263     private void updateNodes(final DataTreeCandidateNode table, final RouterId peerUuid,
264             final DOMDataTreeWriteOperations tx, final Map<RouteUpdateKey, RouteEntry<C, S>> routes) {
265         final var modifiedAttrs = table.modifiedChild(ATTRIBUTES_NID);
266         if (modifiedAttrs != null) {
267             final var newAttValue = modifiedAttrs.dataAfter();
268             if (newAttValue != null) {
269                 LOG.trace("Uptodate found for {}", newAttValue);
270                 tx.put(LogicalDatastoreType.OPERATIONAL, locRibTableIID.node(ATTRIBUTES_NID), newAttValue);
271             }
272         }
273         final var modifiedRoutes = table.modifiedChild(ROUTES_NID);
274         if (modifiedRoutes != null) {
275             updateRoutesEntries(ribSupport.changedRoutes(modifiedRoutes), peerUuid, routes);
276         }
277     }
278
279     private void updateRoutesEntries(final Collection<DataTreeCandidateNode> collection,
280             final RouterId routerId, final Map<RouteUpdateKey, RouteEntry<C, S>> routes) {
281         for (final DataTreeCandidateNode route : collection) {
282             final PathArgument routeArg = route.name();
283             if (!(routeArg instanceof NodeIdentifierWithPredicates routeId)) {
284                 LOG.debug("Route {} already deleted", routeArg);
285                 return;
286             }
287
288             final String routeKey = ribSupport.extractRouteKey(routeId);
289             final Uint32 pathId = ribSupport.extractPathId(routeId);
290
291             RouteEntry<C, S> entry;
292             switch (route.modificationType()) {
293                 case DELETE:
294                     entry = routeEntries.get(routeKey);
295                     if (entry != null) {
296                         totalPathsCounter.decrement();
297                         if (entry.removeRoute(routerId, pathId)) {
298                             routeEntries.remove(routeKey);
299                             totalPrefixesCounter.decrement();
300                             LOG.trace("Removed route from {}", routerId);
301                         }
302                     }
303                     break;
304                 case SUBTREE_MODIFIED:
305                 case WRITE:
306                     entry = routeEntries.get(routeKey);
307                     if (entry == null) {
308                         entry = createEntry(routeKey);
309                     }
310
311                     final var routeAfter = route.getDataAfter();
312                     verify(routeAfter instanceof MapEntryNode, "Unexpected route %s", routeAfter);
313                     entry.addRoute(routerId, pathId, (MapEntryNode) routeAfter);
314                     totalPathsCounter.increment();
315                     break;
316                 default:
317                     throw new IllegalStateException("Unhandled route modification " + route);
318             }
319
320             final RouteUpdateKey routeUpdateKey = new RouteUpdateKey(routerId, routeKey);
321             LOG.debug("Updated route {} entry {}", routeKey, entry);
322             routes.put(routeUpdateKey, entry);
323         }
324     }
325
326     private void walkThrough(final DOMDataTreeWriteOperations tx,
327             final Set<Entry<RouteUpdateKey, RouteEntry<C, S>>> toUpdate) {
328         final List<StaleBestPathRoute> staleRoutes = new ArrayList<>();
329         final List<AdvertizedRoute<C, S>> newRoutes = new ArrayList<>();
330         for (final Entry<RouteUpdateKey, RouteEntry<C, S>> e : toUpdate) {
331             LOG.trace("Walking through {}", e);
332             final RouteEntry<C, S> entry = e.getValue();
333
334             if (!entry.selectBest(ribSupport, ourAs)) {
335                 LOG.trace("Best path has not changed, continuing");
336                 continue;
337             }
338
339             entry.removeStalePaths(ribSupport, e.getKey().getRouteId()).ifPresent(staleRoutes::add);
340             newRoutes.addAll(entry.newBestPaths(ribSupport, e.getKey().getRouteId()));
341         }
342         updateLocRib(newRoutes, staleRoutes, tx);
343         peerTracker.getNonInternalPeers().parallelStream()
344                 .filter(toPeer -> toPeer.supportsTable(entryDep.getLocalTablesKey()))
345                 .forEach(toPeer -> toPeer.refreshRibOut(entryDep, staleRoutes, newRoutes));
346     }
347
348     private void updateLocRib(final List<AdvertizedRoute<C, S>> newRoutes, final List<StaleBestPathRoute> staleRoutes,
349             final DOMDataTreeWriteOperations tx) {
350         final YangInstanceIdentifier locRibTarget = entryDep.getLocRibTableTarget();
351
352         for (final StaleBestPathRoute staleContainer : staleRoutes) {
353             for (final NodeIdentifierWithPredicates routeId : staleContainer.getStaleRouteKeyIdentifiers()) {
354                 final YangInstanceIdentifier routeTarget = ribSupport.createRouteIdentifier(locRibTarget, routeId);
355                 LOG.debug("Delete route from LocRib {}", routeTarget);
356                 tx.delete(LogicalDatastoreType.OPERATIONAL, routeTarget);
357             }
358         }
359
360         for (final AdvertizedRoute<C, S> advRoute : newRoutes) {
361             final MapEntryNode route = advRoute.getRoute();
362             final NodeIdentifierWithPredicates iid = advRoute.getAddPathRouteKeyIdentifier();
363             final YangInstanceIdentifier locRibRouteTarget = ribSupport.createRouteIdentifier(locRibTarget, iid);
364             LOG.debug("Write LocRib route {}", locRibRouteTarget);
365             if (LOG.isTraceEnabled()) {
366                 LOG.trace("Write route to LocRib {}", NormalizedNodes.toStringTree(route));
367             }
368             tx.put(LogicalDatastoreType.OPERATIONAL, locRibRouteTarget, route);
369         }
370     }
371
372     @Override
373     public long getPrefixesCount() {
374         return totalPrefixesCounter.longValue();
375     }
376
377     @Override
378     public long getPathsCount() {
379         return totalPathsCounter.longValue();
380     }
381
382     TablesKey getTableKey() {
383         return ribSupport.getTablesKey();
384     }
385
386     @Override
387     public synchronized void refreshTable(final TablesKey tk, final PeerId peerId) {
388         final org.opendaylight.protocol.bgp.rib.spi.Peer toPeer = peerTracker.getPeer(peerId);
389         if (toPeer != null && toPeer.supportsTable(entryDep.getLocalTablesKey())) {
390             LOG.debug("Peer {} table has been created, inserting existent routes", toPeer.getPeerId());
391             final List<ActualBestPathRoutes<C, S>> routesToStore = new ArrayList<>();
392             for (final Entry<String, RouteEntry<C, S>> entry : routeEntries.entrySet()) {
393                 final List<ActualBestPathRoutes<C, S>> filteredRoute = entry.getValue()
394                         .actualBestPaths(ribSupport, new RouteEntryInfoImpl(toPeer, entry.getKey()));
395                 routesToStore.addAll(filteredRoute);
396             }
397             toPeer.reEvaluateAdvertizement(entryDep, routesToStore);
398         }
399     }
400 }