BGPCEP-754: Rework EffectiveRibInWriter
[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.Optional;
17 import java.util.Set;
18 import java.util.concurrent.atomic.LongAdder;
19 import javax.annotation.Nonnull;
20 import javax.annotation.concurrent.GuardedBy;
21 import javax.annotation.concurrent.NotThreadSafe;
22 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
23 import org.opendaylight.controller.md.sal.dom.api.ClusteredDOMDataTreeChangeListener;
24 import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeChangeService;
25 import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeIdentifier;
26 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
27 import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
28 import org.opendaylight.protocol.bgp.mode.api.PathSelectionMode;
29 import org.opendaylight.protocol.bgp.mode.api.RouteEntry;
30 import org.opendaylight.protocol.bgp.rib.impl.spi.RIBSupportContextRegistry;
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.ExportPolicyPeerTracker;
34 import org.opendaylight.protocol.bgp.rib.spi.IdentifierUtils;
35 import org.opendaylight.protocol.bgp.rib.spi.PeerExportGroup;
36 import org.opendaylight.protocol.bgp.rib.spi.RIBSupport;
37 import org.opendaylight.protocol.bgp.rib.spi.RibSupportUtils;
38 import org.opendaylight.protocol.bgp.rib.spi.RouterIds;
39 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.AsNumber;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev171207.PeerId;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev171207.PeerRole;
42 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev171207.bgp.rib.rib.LocRib;
43 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev171207.bgp.rib.rib.Peer;
44 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev171207.bgp.rib.rib.peer.EffectiveRibIn;
45 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev171207.rib.Tables;
46 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev171207.rib.TablesKey;
47 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev171207.rib.tables.Attributes;
48 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev171207.rib.tables.Routes;
49 import org.opendaylight.yangtools.concepts.ListenerRegistration;
50 import org.opendaylight.yangtools.yang.common.QName;
51 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
52 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
53 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
54 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
55 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
56 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
57 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
58 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
59 import org.slf4j.Logger;
60 import org.slf4j.LoggerFactory;
61
62 @NotThreadSafe
63 final class LocRibWriter implements AutoCloseable, TotalPrefixesCounter, TotalPathsCounter,
64         ClusteredDOMDataTreeChangeListener {
65
66     private static final Logger LOG = LoggerFactory.getLogger(LocRibWriter.class);
67
68     private static final LeafNode<Boolean> ATTRIBUTES_UPTODATE_TRUE = ImmutableNodes
69             .leafNode(QName.create(Attributes.QNAME, "uptodate"), Boolean.TRUE);
70
71     private final Map<NodeIdentifierWithPredicates, RouteEntry> routeEntries = new HashMap<>();
72     private final YangInstanceIdentifier locRibTarget;
73     private final NodeIdentifierWithPredicates tableKey;
74     private final ExportPolicyPeerTracker exportPolicyPeerTracker;
75     private final NodeIdentifier attributesIdentifier;
76     private final Long ourAs;
77     private final RIBSupport ribSupport;
78     private final YangInstanceIdentifier target;
79     private final DOMDataTreeChangeService service;
80     private final PathSelectionMode pathSelectionMode;
81     private final LongAdder totalPathsCounter = new LongAdder();
82     private final LongAdder totalPrefixesCounter = new LongAdder();
83     private final RouteEntryDependenciesContainerImpl entryDep;
84     private DOMTransactionChain chain;
85     @GuardedBy("this")
86     private ListenerRegistration<LocRibWriter> reg;
87
88     private LocRibWriter(final RIBSupportContextRegistry registry, final DOMTransactionChain chain,
89             final YangInstanceIdentifier target, final Long ourAs, final DOMDataTreeChangeService service,
90             final ExportPolicyPeerTracker exportPolicyPeerTracker, final TablesKey tablesKey,
91             final PathSelectionMode pathSelectionMode) {
92         this.chain = requireNonNull(chain);
93         this.target = requireNonNull(target);
94         this.tableKey = RibSupportUtils.toYangTablesKey(requireNonNull(tablesKey));
95         this.locRibTarget = YangInstanceIdentifier.create(target.node(LocRib.QNAME).node(Tables.QNAME)
96                 .node(this.tableKey).getPathArguments());
97         this.ourAs = requireNonNull(ourAs);
98         this.service = requireNonNull(service);
99         this.ribSupport = registry.getRIBSupportContext(tablesKey).getRibSupport();
100         this.attributesIdentifier = this.ribSupport.routeAttributesIdentifier();
101         this.exportPolicyPeerTracker = exportPolicyPeerTracker;
102         this.pathSelectionMode = pathSelectionMode;
103
104         this.entryDep = new RouteEntryDependenciesContainerImpl(this.ribSupport,
105                 tablesKey, this.locRibTarget, this.exportPolicyPeerTracker);
106         init();
107     }
108
109     private synchronized void init() {
110         final DOMDataWriteTransaction tx = this.chain.newWriteOnlyTransaction();
111         tx.merge(LogicalDatastoreType.OPERATIONAL, this.locRibTarget.node(Routes.QNAME), this.ribSupport.emptyRoutes());
112         tx.merge(LogicalDatastoreType.OPERATIONAL, this.locRibTarget.node(Attributes.QNAME)
113                 .node(ATTRIBUTES_UPTODATE_TRUE.getNodeType()), ATTRIBUTES_UPTODATE_TRUE);
114         tx.submit();
115
116         final YangInstanceIdentifier tableId = this.target.node(Peer.QNAME).node(Peer.QNAME).node(EffectiveRibIn.QNAME)
117                 .node(Tables.QNAME).node(this.tableKey);
118         final DOMDataTreeIdentifier wildcard = new DOMDataTreeIdentifier(LogicalDatastoreType.OPERATIONAL, tableId);
119         this.reg = this.service.registerDataTreeChangeListener(wildcard, this);
120     }
121
122     public static LocRibWriter create(@Nonnull final RIBSupportContextRegistry registry,
123             @Nonnull final TablesKey tablesKey,
124             @Nonnull final DOMTransactionChain chain,
125             @Nonnull final YangInstanceIdentifier target,
126             @Nonnull final AsNumber ourAs,
127             @Nonnull final DOMDataTreeChangeService service,
128             @Nonnull final ExportPolicyPeerTracker ep,
129             @Nonnull final PathSelectionMode pathSelectionStrategy) {
130         return new LocRibWriter(registry, chain, target, ourAs.getValue(), service, ep, tablesKey,
131                 pathSelectionStrategy);
132     }
133
134     /**
135      * Re-initialize this LocRibWriter with new transaction chain.
136      *
137      * @param newChain new transaction chain
138      */
139     synchronized void restart(@Nonnull final DOMTransactionChain newChain) {
140         requireNonNull(newChain);
141         close();
142         this.chain = newChain;
143         init();
144     }
145
146     @Override
147     public synchronized void close() {
148         if (this.reg != null) {
149             this.reg.close();
150             this.reg = null;
151         }
152         this.chain.close();
153     }
154
155     @Nonnull
156     private RouteEntry createEntry(final NodeIdentifierWithPredicates routeId) {
157         final RouteEntry ret = this.pathSelectionMode.createRouteEntry(this.ribSupport.isComplexRoute());
158         this.routeEntries.put(routeId, ret);
159         this.totalPrefixesCounter.increment();
160         LOG.trace("Created new entry for {}", routeId);
161         return ret;
162     }
163
164     /**
165      * We use two-stage processing here in hopes that we avoid duplicate
166      * calculations when multiple peers have changed a particular entry.
167      *
168      * @param changes on supported table
169      */
170     @Override
171     public void onDataTreeChanged(final Collection<DataTreeCandidate> changes) {
172         LOG.trace("Received data change {} to LocRib {}", changes, this);
173
174         final DOMDataWriteTransaction tx = this.chain.newWriteOnlyTransaction();
175         try {
176             final Map<RouteUpdateKey, RouteEntry> toUpdate = update(tx, changes);
177
178             if (!toUpdate.isEmpty()) {
179                 walkThrough(tx, toUpdate.entrySet());
180             }
181         } catch (final Exception e) {
182             LOG.error("Failed to completely propagate updates {}, state is undefined", changes, e);
183         } finally {
184             tx.submit();
185         }
186     }
187
188     private Map<RouteUpdateKey, RouteEntry> update(final DOMDataWriteTransaction tx,
189             final Collection<DataTreeCandidate> changes) {
190         final Map<RouteUpdateKey, RouteEntry> ret = new HashMap<>();
191         changes.forEach(tc -> {
192             final DataTreeCandidateNode table = tc.getRootNode();
193             final YangInstanceIdentifier rootPath = tc.getRootPath();
194             final PeerId peerId = IdentifierUtils.peerKeyToPeerId(rootPath);
195             initializeTableWithExistentRoutes(table, peerId, rootPath, tx);
196             updateNodes(table, peerId, tx, ret);
197         });
198         return ret;
199     }
200
201     private void initializeTableWithExistentRoutes(final DataTreeCandidateNode table, final PeerId peerIdOfNewPeer,
202             final YangInstanceIdentifier rootPath, final DOMDataWriteTransaction tx) {
203         if (!table.getDataBefore().isPresent() && this.exportPolicyPeerTracker.isTableSupported(peerIdOfNewPeer)) {
204             this.exportPolicyPeerTracker.registerPeerAsInitialized(peerIdOfNewPeer);
205             LOG.debug("Peer {} table has been created, inserting existent routes", peerIdOfNewPeer);
206             if (this.routeEntries.isEmpty()) {
207                 return;
208             }
209             final PeerRole newPeerRole = this.exportPolicyPeerTracker.getRole(IdentifierUtils.peerPath(rootPath));
210             final PeerExportGroup peerGroup = this.exportPolicyPeerTracker.getPeerGroup(newPeerRole);
211             this.routeEntries.forEach((key, value) -> value.initializeBestPaths(this.entryDep,
212                     new RouteEntryInfoImpl(peerIdOfNewPeer, key, rootPath.getParent().getParent().getParent()),
213                     peerGroup, tx));
214         }
215     }
216
217     private void updateNodes(final DataTreeCandidateNode table, final PeerId peerId, final DOMDataWriteTransaction tx,
218             final Map<RouteUpdateKey, RouteEntry> routes) {
219         for (final DataTreeCandidateNode child : table.getChildNodes()) {
220             LOG.debug("Modification type {}", child.getModificationType());
221             if (Attributes.QNAME.equals(child.getIdentifier().getNodeType())) {
222                 if (child.getDataAfter().isPresent()) {
223                     // putting uptodate attribute in
224                     LOG.trace("Uptodate found for {}", child.getDataAfter());
225                     tx.put(LogicalDatastoreType.OPERATIONAL, this.locRibTarget.node(child.getIdentifier()),
226                             child.getDataAfter().get());
227                 }
228                 continue;
229             }
230             updateRoutesEntries(child, peerId, routes);
231         }
232     }
233
234     private void updateRoutesEntries(final DataTreeCandidateNode child, final PeerId peerId,
235             final Map<RouteUpdateKey, RouteEntry> routes) {
236         final UnsignedInteger routerId = RouterIds.routerIdForPeerId(peerId);
237         final Collection<DataTreeCandidateNode> modifiedRoutes = this.ribSupport.changedDOMRoutes(child);
238         for (final DataTreeCandidateNode route : modifiedRoutes) {
239             final NodeIdentifierWithPredicates routeId = this.ribSupport
240                     .createRouteKeyPathArgument((NodeIdentifierWithPredicates) route.getIdentifier());
241             RouteEntry entry = this.routeEntries.get(routeId);
242             final Optional<NormalizedNode<?, ?>> maybeData = route.getDataAfter();
243             final Optional<NormalizedNode<?, ?>> maybeDataBefore = route.getDataBefore();
244             if (maybeData.isPresent()) {
245                 if (entry == null) {
246                     entry = createEntry(routeId);
247                 }
248                 entry.addRoute(routerId, this.ribSupport.extractPathId(maybeData.get()),
249                         this.attributesIdentifier, maybeData.get());
250                 this.totalPathsCounter.increment();
251             } else if (entry != null) {
252                 this.totalPathsCounter.decrement();
253                 if (entry.removeRoute(routerId, this.ribSupport.extractPathId(maybeDataBefore.get()))) {
254                     this.routeEntries.remove(routeId);
255                     this.totalPrefixesCounter.decrement();
256                     LOG.trace("Removed route from {}", routerId);
257                 }
258             }
259             final RouteUpdateKey routeUpdateKey = new RouteUpdateKey(peerId, routeId);
260             LOG.debug("Updated route {} entry {}", routeId, entry);
261             routes.put(routeUpdateKey, entry);
262         }
263     }
264
265     private void walkThrough(final DOMDataWriteTransaction tx,
266             final Set<Map.Entry<RouteUpdateKey, RouteEntry>> toUpdate) {
267         for (final Map.Entry<RouteUpdateKey, RouteEntry> e : toUpdate) {
268             LOG.trace("Walking through {}", e);
269             final RouteEntry entry = e.getValue();
270
271             if (!entry.selectBest(this.ourAs)) {
272                 LOG.trace("Best path has not changed, continuing");
273                 continue;
274             }
275             entry.updateBestPaths(entryDep, e.getKey().getRouteId(), tx);
276         }
277     }
278
279     @Override
280     public long getPrefixesCount() {
281         return this.totalPrefixesCounter.longValue();
282     }
283
284     @Override
285     public long getPathsCount() {
286         return this.totalPathsCounter.longValue();
287     }
288 }