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