2 * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.protocol.bgp.rib.impl;
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;
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;
27 import java.util.Map.Entry;
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;
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 {
81 private static final Logger LOG = LoggerFactory.getLogger(LocRibWriter.class);
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;
95 private DOMTransactionChain chain;
97 private ListenerRegistration<?> reg;
99 private LocRibWriter(final RIBSupport<C, S> ribSupport,
100 final DOMTransactionChain chain,
101 final YangInstanceIdentifier ribIId,
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);
112 locRibTableIID = ribIId.node(LOCRIB_NID).node(TABLES_NID).node(ribSupport.emptyTable().name()).toOptimized();
114 this.ourAs = ourAs.toJava();
115 this.dataBroker = requireNonNull(dataBroker);
116 this.peerTracker = peerTracker;
117 this.pathSelectionMode = pathSelectionMode;
119 entryDep = new RouteEntryDependenciesContainerImpl(this.ribSupport, this.peerTracker, ribPolicies,
120 afiSafiType, locRibTableIID);
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);
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>() {
146 public void onSuccess(final CommitInfo result) {
147 LOG.trace("Successful commit");
151 public void onFailure(final Throwable trw) {
152 LOG.error("Failed commit", trw);
154 }, MoreExecutors.directExecutor());
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);
162 * Re-initialize this LocRibWriter with new transaction chain.
164 * @param newChain new transaction chain
166 synchronized void restart(final @NonNull DOMTransactionChain newChain) {
167 requireNonNull(newChain);
174 public synchronized void close() {
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);
194 public synchronized void onInitialData() {
195 // FIXME: we need to do something
199 * We use two-stage processing here in hopes that we avoid duplicate
200 * calculations when multiple peers have changed a particular entry.
202 * @param changes on supported table
205 @SuppressWarnings("checkstyle:illegalCatch")
206 public synchronized void onDataTreeChanged(final List<DataTreeCandidate> changes) {
208 LOG.trace("Chain closed, ignoring received data change {} to LocRib {}", changes, this);
211 LOG.trace("Received data change {} to LocRib {}", changes, this);
212 final DOMDataTreeWriteTransaction tx = chain.newWriteOnlyTransaction();
214 final Map<RouteUpdateKey, RouteEntry<C, S>> toUpdate = update(tx, changes);
216 if (!toUpdate.isEmpty()) {
217 walkThrough(tx, toUpdate.entrySet());
219 } catch (final Exception e) {
220 LOG.error("Failed to completely propagate updates {}, state is undefined", changes, e);
222 tx.commit().addCallback(new FutureCallback<CommitInfo>() {
224 public void onSuccess(final CommitInfo result) {
225 LOG.trace("Successful commit");
229 public void onFailure(final Throwable trw) {
230 LOG.error("Failed commit", trw);
232 }, MoreExecutors.directExecutor());
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()));
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);
255 toPeer.initializeRibOut(entryDep, routesToStore);
258 // Process new routes from Peer
259 updateNodes(table, peerUuid, tx, ret);
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);
274 final var modifiedRoutes = table.modifiedChild(ROUTES_NID);
275 if (modifiedRoutes != null) {
276 updateRoutesEntries(ribSupport.changedRoutes(modifiedRoutes), peerUuid, routes);
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);
289 final String routeKey = ribSupport.extractRouteKey(routeId);
290 final Uint32 pathId = ribSupport.extractPathId(routeId);
292 RouteEntry<C, S> entry;
293 switch (route.getModificationType()) {
295 entry = routeEntries.get(routeKey);
297 totalPathsCounter.decrement();
298 if (entry.removeRoute(routerId, pathId)) {
299 routeEntries.remove(routeKey);
300 totalPrefixesCounter.decrement();
301 LOG.trace("Removed route from {}", routerId);
305 case SUBTREE_MODIFIED:
307 entry = routeEntries.get(routeKey);
309 entry = createEntry(routeKey);
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();
318 throw new IllegalStateException("Unhandled route modification " + route);
321 final RouteUpdateKey routeUpdateKey = new RouteUpdateKey(routerId, routeKey);
322 LOG.debug("Updated route {} entry {}", routeKey, entry);
323 routes.put(routeUpdateKey, entry);
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();
335 if (!entry.selectBest(ribSupport, ourAs)) {
336 LOG.trace("Best path has not changed, continuing");
340 entry.removeStalePaths(ribSupport, e.getKey().getRouteId()).ifPresent(staleRoutes::add);
341 newRoutes.addAll(entry.newBestPaths(ribSupport, e.getKey().getRouteId()));
343 updateLocRib(newRoutes, staleRoutes, tx);
344 peerTracker.getNonInternalPeers().parallelStream()
345 .filter(toPeer -> toPeer.supportsTable(entryDep.getLocalTablesKey()))
346 .forEach(toPeer -> toPeer.refreshRibOut(entryDep, staleRoutes, newRoutes));
349 private void updateLocRib(final List<AdvertizedRoute<C, S>> newRoutes, final List<StaleBestPathRoute> staleRoutes,
350 final DOMDataTreeWriteOperations tx) {
351 final YangInstanceIdentifier locRibTarget = entryDep.getLocRibTableTarget();
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);
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));
369 tx.put(LogicalDatastoreType.OPERATIONAL, locRibRouteTarget, route);
374 public long getPrefixesCount() {
375 return totalPrefixesCounter.longValue();
379 public long getPathsCount() {
380 return totalPathsCounter.longValue();
383 TablesKey getTableKey() {
384 return ribSupport.getTablesKey();
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);
398 toPeer.reEvaluateAdvertizement(entryDep, routesToStore);