Provide Add Path support for all AFI/SAFI
[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.primitives.UnsignedInteger;
13 import java.util.Collection;
14 import java.util.HashMap;
15 import java.util.Map;
16 import java.util.Set;
17 import java.util.concurrent.atomic.LongAdder;
18 import javax.annotation.Nonnull;
19 import javax.annotation.concurrent.GuardedBy;
20 import javax.annotation.concurrent.NotThreadSafe;
21 import org.opendaylight.controller.md.sal.binding.api.BindingTransactionChain;
22 import org.opendaylight.controller.md.sal.binding.api.ClusteredDataTreeChangeListener;
23 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
24 import org.opendaylight.controller.md.sal.binding.api.DataObjectModification;
25 import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier;
26 import org.opendaylight.controller.md.sal.binding.api.DataTreeModification;
27 import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
28 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
29 import org.opendaylight.protocol.bgp.mode.api.PathSelectionMode;
30 import org.opendaylight.protocol.bgp.mode.api.RouteEntry;
31 import org.opendaylight.protocol.bgp.rib.impl.state.rib.TotalPathsCounter;
32 import org.opendaylight.protocol.bgp.rib.impl.state.rib.TotalPrefixesCounter;
33 import org.opendaylight.protocol.bgp.rib.spi.BGPPeerTracker;
34 import org.opendaylight.protocol.bgp.rib.spi.RIBSupport;
35 import org.opendaylight.protocol.bgp.rib.spi.RouterIds;
36 import org.opendaylight.protocol.bgp.rib.spi.policy.BGPRibRoutingPolicy;
37 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.AsNumber;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.Route;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.bgp.rib.Rib;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.bgp.rib.RibKey;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.bgp.rib.rib.LocRib;
42 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.bgp.rib.rib.Peer;
43 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.bgp.rib.rib.PeerKey;
44 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.bgp.rib.rib.peer.EffectiveRibIn;
45 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.Tables;
46 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.TablesKey;
47 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.tables.Attributes;
48 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev180329.rib.tables.AttributesBuilder;
49 import org.opendaylight.yangtools.concepts.ListenerRegistration;
50 import org.opendaylight.yangtools.yang.binding.DataObject;
51 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
52 import org.opendaylight.yangtools.yang.binding.KeyedInstanceIdentifier;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
55
56 @NotThreadSafe
57 final class LocRibWriter implements AutoCloseable, TotalPrefixesCounter, TotalPathsCounter,
58         ClusteredDataTreeChangeListener<Tables> {
59
60     private static final Logger LOG = LoggerFactory.getLogger(LocRibWriter.class);
61
62     private final Map<String, RouteEntry> routeEntries = new HashMap<>();
63     private final Long ourAs;
64     private final RIBSupport ribSupport;
65     private final DataBroker dataBroker;
66     private final PathSelectionMode pathSelectionMode;
67     private final LongAdder totalPathsCounter = new LongAdder();
68     private final LongAdder totalPrefixesCounter = new LongAdder();
69     private final RouteEntryDependenciesContainerImpl entryDep;
70     private final BGPPeerTracker peerTracker;
71     private final KeyedInstanceIdentifier<Rib, RibKey> ribIId;
72     private final TablesKey tk;
73     private final KeyedInstanceIdentifier<Tables, TablesKey> locRibTableIID;
74     private BindingTransactionChain chain;
75     @GuardedBy("this")
76     private ListenerRegistration<LocRibWriter> reg;
77
78     private LocRibWriter(final RIBSupport ribSupport,
79             final BindingTransactionChain chain,
80             final KeyedInstanceIdentifier<Rib, RibKey> ribIId,
81             final Long ourAs,
82             final DataBroker dataBroker,
83             final BGPRibRoutingPolicy ribPolicies,
84             final BGPPeerTracker peerTracker,
85             final TablesKey tablesKey,
86             final PathSelectionMode pathSelectionMode) {
87         this.chain = requireNonNull(chain);
88         this.ribIId = requireNonNull(ribIId);
89         this.tk = requireNonNull(tablesKey);
90         this.locRibTableIID = ribIId.child(LocRib.class).child(Tables.class, this.tk);
91         this.ourAs = requireNonNull(ourAs);
92         this.dataBroker = requireNonNull(dataBroker);
93         this.ribSupport = requireNonNull(ribSupport);
94         this.peerTracker = peerTracker;
95         this.pathSelectionMode = pathSelectionMode;
96
97         this.entryDep = new RouteEntryDependenciesContainerImpl(this.ribSupport, ribPolicies,
98                 tablesKey, this.locRibTableIID);
99         init();
100     }
101
102     public static LocRibWriter create(@Nonnull final RIBSupport ribSupport,
103             @Nonnull final TablesKey tablesKey,
104             @Nonnull final BindingTransactionChain chain,
105             @Nonnull final KeyedInstanceIdentifier<Rib, RibKey> ribIId,
106             @Nonnull final AsNumber ourAs,
107             @Nonnull final DataBroker dataBroker,
108             final BGPRibRoutingPolicy ribPolicies,
109             @Nonnull final BGPPeerTracker peerTracker,
110             @Nonnull final PathSelectionMode pathSelectionStrategy) {
111         return new LocRibWriter(ribSupport, chain, ribIId, ourAs.getValue(), dataBroker, ribPolicies,
112                 peerTracker, tablesKey, pathSelectionStrategy);
113     }
114
115     @SuppressWarnings("unchecked")
116     private synchronized void init() {
117         final WriteTransaction tx = this.chain.newWriteOnlyTransaction();
118         tx.merge(LogicalDatastoreType.OPERATIONAL,
119                 this.locRibTableIID.builder().child(Attributes.class).build(),
120                 new AttributesBuilder().setUptodate(true).build());
121         tx.submit();
122
123         final InstanceIdentifier<Tables> tableId = this.ribIId.builder().child(Peer.class)
124                 .child(EffectiveRibIn.class).child(Tables.class, this.tk).build();
125         this.reg = this.dataBroker.registerDataTreeChangeListener(
126                 new DataTreeIdentifier(LogicalDatastoreType.OPERATIONAL, tableId), this);
127     }
128
129     /**
130      * Re-initialize this LocRibWriter with new transaction chain.
131      *
132      * @param newChain new transaction chain
133      */
134     synchronized void restart(@Nonnull final BindingTransactionChain newChain) {
135         requireNonNull(newChain);
136         close();
137         this.chain = newChain;
138         init();
139     }
140
141     @Override
142     public synchronized void close() {
143         if (this.reg != null) {
144             this.reg.close();
145             this.reg = null;
146         }
147         this.chain.close();
148     }
149
150     @Nonnull
151     private RouteEntry createEntry(final String routeId) {
152         final RouteEntry ret = this.pathSelectionMode.createRouteEntry();
153         this.routeEntries.put(routeId, ret);
154         this.totalPrefixesCounter.increment();
155         LOG.trace("Created new entry for {}", routeId);
156         return ret;
157     }
158
159     /**
160      * We use two-stage processing here in hopes that we avoid duplicate
161      * calculations when multiple peers have changed a particular entry.
162      *
163      * @param changes on supported table
164      */
165     @Override
166     public void onDataTreeChanged(final Collection<DataTreeModification<Tables>> changes) {
167         LOG.trace("Received data change {} to LocRib {}", changes, this);
168
169         final WriteTransaction tx = this.chain.newWriteOnlyTransaction();
170         try {
171             final Map<RouteUpdateKey, RouteEntry> toUpdate = update(tx, changes);
172
173             if (!toUpdate.isEmpty()) {
174                 walkThrough(tx, toUpdate.entrySet());
175             }
176         } catch (final Exception e) {
177             LOG.error("Failed to completely propagate updates {}, state is undefined", changes, e);
178         } finally {
179             tx.submit();
180         }
181     }
182
183     @SuppressWarnings("unchecked")
184     private Map<RouteUpdateKey, RouteEntry> update(final WriteTransaction tx,
185             final Collection<DataTreeModification<Tables>> changes) {
186         final Map<RouteUpdateKey, RouteEntry> ret = new HashMap<>();
187         for (final DataTreeModification<Tables> tc : changes) {
188             final DataObjectModification<Tables> table = tc.getRootNode();
189             final DataTreeIdentifier<Tables> rootPath = tc.getRootPath();
190             final KeyedInstanceIdentifier<Peer, PeerKey> peerKIid = (KeyedInstanceIdentifier<Peer, PeerKey>)
191                     rootPath.getRootIdentifier().firstIdentifierOf(Peer.class);
192             final UnsignedInteger peerUuid = RouterIds.routerIdForPeerId(peerKIid.getKey().getPeerId());
193             /*
194             Initialize Peer with routes under loc rib
195              */
196             if (!this.routeEntries.isEmpty() && table.getDataBefore() == null) {
197                 final org.opendaylight.protocol.bgp.rib.spi.Peer peer
198                         = this.peerTracker.getPeer(peerKIid.getKey().getPeerId());
199                 if (peer != null && peer.supportsTable(this.entryDep.getLocalTablesKey())) {
200                     LOG.debug("Peer {} table has been created, inserting existent routes", peer.getPeerId());
201                     this.routeEntries.forEach((key, value) -> value.initializeBestPaths(this.entryDep,
202                             new RouteEntryInfoImpl(peer, key), tx));
203                 }
204             }
205             /*
206             Process new routes from Peer
207              */
208             updateNodes(table, peerUuid, tx, ret);
209         }
210         return ret;
211     }
212
213     @SuppressWarnings("unchecked")
214     private void updateNodes(
215             final DataObjectModification<Tables> table,
216             final UnsignedInteger peerUuid,
217             final WriteTransaction tx,
218             final Map<RouteUpdateKey, RouteEntry> routes
219     ) {
220
221         final DataObjectModification<Attributes> attUpdate = table.getModifiedChildContainer(Attributes.class);
222
223         if (attUpdate != null && attUpdate.getDataAfter() != null) {
224             final Attributes newAttValue = attUpdate.getDataAfter();
225             LOG.trace("Uptodate found for {}", newAttValue);
226             tx.put(LogicalDatastoreType.OPERATIONAL, this.locRibTableIID.child(Attributes.class), newAttValue);
227         }
228
229         final DataObjectModification routesChangesContainer =
230                 table.getModifiedChildContainer(this.ribSupport.routesContainerClass());
231         if (routesChangesContainer == null) {
232             return;
233         }
234         updateRoutesEntries(routesChangesContainer.getModifiedChildren(), peerUuid, routes);
235     }
236
237     @SuppressWarnings("unchecked")
238     private void updateRoutesEntries(
239             final Collection<DataObjectModification<? extends DataObject>> routeChanges,
240             final UnsignedInteger routerId,
241             final Map<RouteUpdateKey, RouteEntry> routes
242     ) {
243         for (final DataObjectModification<? extends DataObject> route : routeChanges) {
244             final Route newRoute = (Route) route.getDataAfter();
245             final Route oldRoute = (Route) route.getDataBefore();
246             String routeKey;
247             RouteEntry entry;
248             if (newRoute != null) {
249                 routeKey = newRoute.getRouteKey();
250                 entry = this.routeEntries.get(routeKey);
251
252                 if (entry == null) {
253                     entry = createEntry(routeKey);
254                 }
255
256                 final long pathId = newRoute.getPathId().getValue();
257                 entry.addRoute(routerId, pathId, newRoute);
258                 this.totalPathsCounter.increment();
259             } else {
260                 routeKey = oldRoute.getRouteKey();
261                 entry = this.routeEntries.get(routeKey);
262                 if(entry != null) {
263                     this.totalPathsCounter.decrement();
264                     final long pathId = oldRoute.getPathId().getValue();
265                     if (entry.removeRoute(routerId, pathId)) {
266                         this.routeEntries.remove(routeKey);
267                         this.totalPrefixesCounter.decrement();
268                         LOG.trace("Removed route from {}", routerId);
269                     }
270                 }
271             }
272             final RouteUpdateKey routeUpdateKey = new RouteUpdateKey(routerId, routeKey);
273             LOG.debug("Updated route {} entry {}", routeKey, entry);
274             routes.put(routeUpdateKey, entry);
275         }
276     }
277
278     private void walkThrough(final WriteTransaction tx,
279             final Set<Map.Entry<RouteUpdateKey, RouteEntry>> toUpdate) {
280         for (final Map.Entry<RouteUpdateKey, RouteEntry> e : toUpdate) {
281             LOG.trace("Walking through {}", e);
282             final RouteEntry entry = e.getValue();
283
284             if (!entry.selectBest(this.ourAs)) {
285                 LOG.trace("Best path has not changed, continuing");
286                 continue;
287             }
288             entry.updateBestPaths(entryDep, e.getKey().getRouteId(), tx);
289         }
290     }
291
292     @Override
293     public long getPrefixesCount() {
294         return this.totalPrefixesCounter.longValue();
295     }
296
297     @Override
298     public long getPathsCount() {
299         return this.totalPathsCounter.longValue();
300     }
301
302     public TablesKey getTableKey() {
303         return this.tk;
304     }
305 }