Extract routeKey/pathId from the identifier
[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 java.util.Objects.requireNonNull;
11
12 import com.google.common.util.concurrent.FutureCallback;
13 import com.google.common.util.concurrent.MoreExecutors;
14 import java.util.ArrayList;
15 import java.util.Collection;
16 import java.util.HashMap;
17 import java.util.List;
18 import java.util.Map;
19 import java.util.Map.Entry;
20 import java.util.Set;
21 import java.util.concurrent.atomic.LongAdder;
22 import javax.annotation.Nonnull;
23 import javax.annotation.concurrent.GuardedBy;
24 import javax.annotation.concurrent.NotThreadSafe;
25 import org.opendaylight.controller.md.sal.binding.api.BindingTransactionChain;
26 import org.opendaylight.controller.md.sal.binding.api.ClusteredDataTreeChangeListener;
27 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
28 import org.opendaylight.controller.md.sal.binding.api.DataObjectModification;
29 import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier;
30 import org.opendaylight.controller.md.sal.binding.api.DataTreeModification;
31 import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
32 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
33 import org.opendaylight.mdsal.common.api.CommitInfo;
34 import org.opendaylight.protocol.bgp.mode.api.PathSelectionMode;
35 import org.opendaylight.protocol.bgp.mode.api.RouteEntry;
36 import org.opendaylight.protocol.bgp.rib.impl.spi.RibOutRefresh;
37 import org.opendaylight.protocol.bgp.rib.impl.state.rib.TotalPathsCounter;
38 import org.opendaylight.protocol.bgp.rib.impl.state.rib.TotalPrefixesCounter;
39 import org.opendaylight.protocol.bgp.rib.spi.BGPPeerTracker;
40 import org.opendaylight.protocol.bgp.rib.spi.RIBSupport;
41 import org.opendaylight.protocol.bgp.rib.spi.RouterId;
42 import org.opendaylight.protocol.bgp.rib.spi.entry.ActualBestPathRoutes;
43 import org.opendaylight.protocol.bgp.rib.spi.entry.AdvertizedRoute;
44 import org.opendaylight.protocol.bgp.rib.spi.entry.StaleBestPathRoute;
45 import org.opendaylight.protocol.bgp.rib.spi.policy.BGPRibRoutingPolicy;
46 import org.opendaylight.yang.gen.v1.http.openconfig.net.yang.bgp.types.rev151009.AfiSafiType;
47 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.AsNumber;
48 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.message.rev180329.PathId;
49 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.PeerId;
50 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.Route;
51 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.bgp.rib.Rib;
52 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.bgp.rib.RibKey;
53 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.bgp.rib.rib.LocRib;
54 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.bgp.rib.rib.Peer;
55 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.bgp.rib.rib.PeerKey;
56 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.bgp.rib.rib.peer.EffectiveRibIn;
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.Attributes;
60 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.tables.AttributesBuilder;
61 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.tables.Routes;
62 import org.opendaylight.yangtools.concepts.ListenerRegistration;
63 import org.opendaylight.yangtools.yang.binding.ChildOf;
64 import org.opendaylight.yangtools.yang.binding.ChoiceIn;
65 import org.opendaylight.yangtools.yang.binding.DataObject;
66 import org.opendaylight.yangtools.yang.binding.Identifiable;
67 import org.opendaylight.yangtools.yang.binding.Identifier;
68 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
69 import org.opendaylight.yangtools.yang.binding.KeyedInstanceIdentifier;
70 import org.slf4j.Logger;
71 import org.slf4j.LoggerFactory;
72
73 @NotThreadSafe
74 final class LocRibWriter<C extends Routes & DataObject & ChoiceIn<Tables>, S extends ChildOf<? super C>,
75         R extends Route & ChildOf<? super S> & Identifiable<I>, I extends Identifier<R>>
76         implements AutoCloseable, RibOutRefresh, TotalPrefixesCounter, TotalPathsCounter,
77         ClusteredDataTreeChangeListener<Tables> {
78
79     private static final Logger LOG = LoggerFactory.getLogger(LocRibWriter.class);
80
81     private final Map<String, RouteEntry<C, S, R, I>> routeEntries = new HashMap<>();
82     private final long ourAs;
83     private final RIBSupport<C, S, R, I> ribSupport;
84     private final DataBroker 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 KeyedInstanceIdentifier<Rib, RibKey> ribIId;
91     private final TablesKey tk;
92     private final KeyedInstanceIdentifier<Tables, TablesKey> locRibTableIID;
93
94     private BindingTransactionChain chain;
95     @GuardedBy("this")
96     private ListenerRegistration<LocRibWriter> reg;
97
98     private LocRibWriter(final RIBSupport<C, S, R, I> ribSupport,
99             final BindingTransactionChain chain,
100             final KeyedInstanceIdentifier<Rib, RibKey> ribIId,
101             final Long ourAs,
102             final DataBroker dataBroker,
103             final BGPRibRoutingPolicy ribPolicies,
104             final BGPPeerTracker peerTracker,
105             final TablesKey tablesKey,
106             final Class<? extends AfiSafiType> afiSafiType,
107             final PathSelectionMode pathSelectionMode) {
108         this.chain = requireNonNull(chain);
109         this.ribIId = requireNonNull(ribIId);
110         this.tk = requireNonNull(tablesKey);
111         this.locRibTableIID = ribIId.child(LocRib.class).child(Tables.class, this.tk);
112         this.ourAs = ourAs;
113         this.dataBroker = requireNonNull(dataBroker);
114         this.ribSupport = requireNonNull(ribSupport);
115         this.peerTracker = peerTracker;
116         this.pathSelectionMode = pathSelectionMode;
117
118         this.entryDep = new RouteEntryDependenciesContainerImpl(this.ribSupport, this.peerTracker, ribPolicies,
119                 tablesKey, afiSafiType, this.locRibTableIID);
120         init();
121     }
122
123     public static <C extends Routes & DataObject & ChoiceIn<Tables>, S extends ChildOf<? super C>,
124                 R extends Route & ChildOf<? super S> & Identifiable<I>, I extends Identifier<R>>
125                 LocRibWriter<C, S, R, I> create(
126             @Nonnull final RIBSupport<C, S, R, I> ribSupport,
127             @Nonnull final TablesKey tablesKey,
128             @Nonnull final Class<? extends AfiSafiType> afiSafiType,
129             @Nonnull final BindingTransactionChain chain,
130             @Nonnull final KeyedInstanceIdentifier<Rib, RibKey> ribIId,
131             @Nonnull final AsNumber ourAs,
132             @Nonnull final DataBroker dataBroker,
133             final BGPRibRoutingPolicy ribPolicies,
134             @Nonnull final BGPPeerTracker peerTracker,
135             @Nonnull final PathSelectionMode pathSelectionStrategy) {
136         return new LocRibWriter<>(ribSupport, chain, ribIId, ourAs.getValue(), dataBroker, ribPolicies,
137                 peerTracker, tablesKey, afiSafiType, pathSelectionStrategy);
138     }
139
140     private synchronized void init() {
141         final WriteTransaction tx = this.chain.newWriteOnlyTransaction();
142         tx.merge(LogicalDatastoreType.OPERATIONAL,
143                 this.locRibTableIID.builder().child(Attributes.class).build(),
144                 new AttributesBuilder().setUptodate(true).build());
145         tx.commit().addCallback(new FutureCallback<CommitInfo>() {
146             @Override
147             public void onSuccess(final CommitInfo result) {
148                 LOG.trace("Successful commit");
149             }
150
151             @Override
152             public void onFailure(final Throwable trw) {
153                 LOG.error("Failed commit", trw);
154             }
155         }, MoreExecutors.directExecutor());
156
157         final InstanceIdentifier<Tables> tableId = this.ribIId.builder().child(Peer.class)
158                 .child(EffectiveRibIn.class).child(Tables.class, this.tk).build();
159         this.reg = this.dataBroker.registerDataTreeChangeListener(
160                 new DataTreeIdentifier<>(LogicalDatastoreType.OPERATIONAL, tableId), this);
161     }
162
163     /**
164      * Re-initialize this LocRibWriter with new transaction chain.
165      *
166      * @param newChain new transaction chain
167      */
168     synchronized void restart(@Nonnull final BindingTransactionChain newChain) {
169         requireNonNull(newChain);
170         close();
171         this.chain = newChain;
172         init();
173     }
174
175     @Override
176     public synchronized void close() {
177         if (this.reg != null) {
178             this.reg.close();
179             this.reg = null;
180         }
181         if (this.chain != null) {
182             this.chain.close();
183             this.chain = null;
184         }
185     }
186
187     @Nonnull
188     private RouteEntry<C, S, R, I> createEntry(final String routeId) {
189         final RouteEntry<C, S, R, I> ret = this.pathSelectionMode.createRouteEntry();
190         this.routeEntries.put(routeId, ret);
191         this.totalPrefixesCounter.increment();
192         LOG.trace("Created new entry for {}", routeId);
193         return ret;
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     public synchronized void onDataTreeChanged(final Collection<DataTreeModification<Tables>> changes) {
204         if (this.chain == null) {
205             LOG.trace("Chain closed, ignoring received data change {} to LocRib {}", changes, this);
206             return;
207         }
208         LOG.trace("Received data change {} to LocRib {}", changes, this);
209         final WriteTransaction tx = this.chain.newWriteOnlyTransaction();
210         try {
211             final Map<RouteUpdateKey, RouteEntry<C, S, R, I>> toUpdate = update(tx, changes);
212
213             if (!toUpdate.isEmpty()) {
214                 walkThrough(tx, toUpdate.entrySet());
215             }
216         } catch (final Exception e) {
217             LOG.error("Failed to completely propagate updates {}, state is undefined", changes, e);
218         } finally {
219             tx.commit().addCallback(new FutureCallback<CommitInfo>() {
220                 @Override
221                 public void onSuccess(final CommitInfo result) {
222                     LOG.trace("Successful commit");
223                 }
224
225                 @Override
226                 public void onFailure(final Throwable trw) {
227                     LOG.error("Failed commit", trw);
228                 }
229             }, MoreExecutors.directExecutor());
230         }
231     }
232
233     @SuppressWarnings("unchecked")
234     private Map<RouteUpdateKey, RouteEntry<C, S, R, I>> update(final WriteTransaction tx,
235             final Collection<DataTreeModification<Tables>> changes) {
236         final Map<RouteUpdateKey, RouteEntry<C, S, R, I>> ret = new HashMap<>();
237         for (final DataTreeModification<Tables> tc : changes) {
238             final DataObjectModification<Tables> table = tc.getRootNode();
239             final DataTreeIdentifier<Tables> rootPath = tc.getRootPath();
240             final KeyedInstanceIdentifier<Peer, PeerKey> peerKIid = (KeyedInstanceIdentifier<Peer, PeerKey>)
241                     rootPath.getRootIdentifier().firstIdentifierOf(Peer.class);
242             final RouterId peerUuid = RouterId.forPeerId(peerKIid.getKey().getPeerId());
243             /*
244             Initialize Peer with routes under loc rib
245              */
246             if (!this.routeEntries.isEmpty() && table.getDataBefore() == null) {
247                 final org.opendaylight.protocol.bgp.rib.spi.Peer toPeer
248                         = this.peerTracker.getPeer(peerKIid.getKey().getPeerId());
249                 if (toPeer != null && toPeer.supportsTable(this.entryDep.getLocalTablesKey())) {
250                     LOG.debug("Peer {} table has been created, inserting existent routes", toPeer.getPeerId());
251                     final List<ActualBestPathRoutes<C, S, R, I>> routesToStore = new ArrayList<>();
252                     for (final Entry<String, RouteEntry<C, S, R, I>> entry : this.routeEntries.entrySet()) {
253                         final List<ActualBestPathRoutes<C, S, R, I>> filteredRoute = entry.getValue()
254                                 .actualBestPaths(this.ribSupport, new RouteEntryInfoImpl(toPeer, entry.getKey()));
255                         routesToStore.addAll(filteredRoute);
256                     }
257                     toPeer.initializeRibOut(this.entryDep, routesToStore);
258                 }
259             }
260             /*
261             Process new routes from Peer
262              */
263             updateNodes(table, peerUuid, tx, ret);
264         }
265         return ret;
266     }
267
268     private void updateNodes(final DataObjectModification<Tables> table, final RouterId peerUuid,
269             final WriteTransaction tx, final Map<RouteUpdateKey, RouteEntry<C, S, R, I>> routes) {
270         final DataObjectModification<Attributes> attUpdate = table.getModifiedChildContainer(Attributes.class);
271         if (attUpdate != null) {
272             final Attributes newAttValue = attUpdate.getDataAfter();
273             if (newAttValue != null) {
274                 LOG.trace("Uptodate found for {}", newAttValue);
275                 tx.put(LogicalDatastoreType.OPERATIONAL, this.locRibTableIID.child(Attributes.class), newAttValue);
276             }
277         }
278
279         final DataObjectModification<S> routesChangesContainer
280                 = table.getModifiedChildContainer(ribSupport.routesCaseClass(), ribSupport.routesContainerClass());
281         if (routesChangesContainer != null) {
282             updateRoutesEntries(routesChangesContainer.getModifiedChildren(), peerUuid, routes);
283         }
284     }
285
286     private void updateRoutesEntries(final Collection<? extends DataObjectModification<?>> collection,
287             final RouterId routerId, final Map<RouteUpdateKey, RouteEntry<C, S, R, I>> routes) {
288         for (final DataObjectModification<? extends DataObject> route : collection) {
289             if (!(route.getIdentifier() instanceof InstanceIdentifier.IdentifiableItem)) {
290                 LOG.debug("Route {} already deleted", route.getIdentifier());
291                 return;
292             }
293             final I routeListKey = (I) ((InstanceIdentifier.IdentifiableItem) route.getIdentifier()).getKey();
294             final String routeKey = ribSupport.extractRouteKey(routeListKey);
295             final PathId pathId = ribSupport.extractPathId(routeListKey);
296
297             RouteEntry<C, S, R, I> entry;
298             switch (route.getModificationType()) {
299                 case DELETE:
300                     entry = this.routeEntries.get(routeKey);
301                     if (entry != null) {
302                         this.totalPathsCounter.decrement();
303                         if (entry.removeRoute(routerId, pathId.getValue())) {
304                             this.routeEntries.remove(routeKey);
305                             this.totalPrefixesCounter.decrement();
306                             LOG.trace("Removed route from {}", routerId);
307                         }
308                     }
309                     break;
310                 case SUBTREE_MODIFIED:
311                 case WRITE:
312                     final R newRoute = (R) route.getDataAfter();
313                     entry = this.routeEntries.get(routeKey);
314                     if (entry == null) {
315                         entry = createEntry(routeKey);
316                     }
317
318                     entry.addRoute(routerId, pathId.getValue(), newRoute);
319                     this.totalPathsCounter.increment();
320                     break;
321                 default:
322                     throw new IllegalStateException("Unhandled route modification " + route);
323             }
324
325             final RouteUpdateKey routeUpdateKey = new RouteUpdateKey(routerId, routeKey);
326             LOG.debug("Updated route {} entry {}", routeKey, entry);
327             routes.put(routeUpdateKey, entry);
328         }
329     }
330
331     private void walkThrough(final WriteTransaction tx,
332             final Set<Entry<RouteUpdateKey, RouteEntry<C, S, R, I>>> toUpdate) {
333         final List<StaleBestPathRoute<C, S, R, I>> staleRoutes = new ArrayList<>();
334         final List<AdvertizedRoute<C, S, R, I>> newRoutes = new ArrayList<>();
335         for (final Entry<RouteUpdateKey, RouteEntry<C, S, R, I>> e : toUpdate) {
336             LOG.trace("Walking through {}", e);
337             final RouteEntry<C, S, R, I> entry = e.getValue();
338
339             if (!entry.selectBest(this.ourAs)) {
340                 LOG.trace("Best path has not changed, continuing");
341                 continue;
342             }
343
344             entry.removeStalePaths(this.ribSupport, e.getKey().getRouteId()).ifPresent(staleRoutes::add);
345             newRoutes.addAll(entry.newBestPaths(this.ribSupport, e.getKey().getRouteId()));
346         }
347         updateLocRib(newRoutes, staleRoutes, tx);
348         this.peerTracker.getNonInternalPeers().parallelStream()
349                 .forEach(toPeer->toPeer.refreshRibOut(this.entryDep, staleRoutes, newRoutes));
350     }
351
352     private void updateLocRib(final List<AdvertizedRoute<C, S, R, I>> newRoutes,
353             final List<StaleBestPathRoute<C, S, R, I>> staleRoutes,
354             final WriteTransaction tx) {
355         final KeyedInstanceIdentifier<Tables, TablesKey> locRibTarget = this.entryDep.getLocRibTableTarget();
356
357         for (final StaleBestPathRoute<C, S, R, I> staleContainer : staleRoutes) {
358             for (final I routeId : staleContainer.getStaleRouteKeyIdentifiers()) {
359                 final InstanceIdentifier<R> routeTarget = ribSupport.createRouteIdentifier(locRibTarget, routeId);
360                 LOG.debug("Delete route from LocRib {}", routeTarget);
361                 tx.delete(LogicalDatastoreType.OPERATIONAL, routeTarget);
362             }
363         }
364
365         for (final AdvertizedRoute<C,S,R,I> advRoute : newRoutes) {
366             final R route = advRoute.getRoute();
367             final I iid = advRoute.getAddPathRouteKeyIdentifier();
368             final InstanceIdentifier<R> locRibRouteTarget
369                     = this.ribSupport.createRouteIdentifier(locRibTarget, iid);
370             LOG.debug("Write route to LocRib {}", route);
371             tx.put(LogicalDatastoreType.OPERATIONAL, locRibRouteTarget, route);
372         }
373     }
374
375     @Override
376     public long getPrefixesCount() {
377         return this.totalPrefixesCounter.longValue();
378     }
379
380     @Override
381     public long getPathsCount() {
382         return this.totalPathsCounter.longValue();
383     }
384
385     TablesKey getTableKey() {
386         return this.tk;
387     }
388
389     @Override
390     public synchronized void refreshTable(final TablesKey tk, final PeerId peerId) {
391         final org.opendaylight.protocol.bgp.rib.spi.Peer toPeer = this.peerTracker.getPeer(peerId);
392         if (toPeer != null && toPeer.supportsTable(this.entryDep.getLocalTablesKey())) {
393             LOG.debug("Peer {} table has been created, inserting existent routes", toPeer.getPeerId());
394             final List<ActualBestPathRoutes<C, S, R, I>> routesToStore = new ArrayList<>();
395             for (final Entry<String, RouteEntry<C, S, R, I>> entry : this.routeEntries.entrySet()) {
396                 final List<ActualBestPathRoutes<C, S, R, I>> filteredRoute = entry.getValue()
397                         .actualBestPaths(this.ribSupport, new RouteEntryInfoImpl(toPeer, entry.getKey()));
398                 routesToStore.addAll(filteredRoute);
399             }
400             toPeer.reEvaluateAdvertizement(this.entryDep, routesToStore);
401         }
402     }
403 }