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