bdd4fb8000ddc4cbf47c5ad4228fedf7a8c45322
[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.YangInstanceIdentifier.PathArgument;
55 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
56 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
57 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
58 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
59 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
60 import org.slf4j.Logger;
61 import org.slf4j.LoggerFactory;
62
63 @NotThreadSafe
64 final class LocRibWriter implements AutoCloseable, TotalPrefixesCounter, TotalPathsCounter,
65     ClusteredDOMDataTreeChangeListener {
66
67     private static final Logger LOG = LoggerFactory.getLogger(LocRibWriter.class);
68
69     private static final LeafNode<Boolean> ATTRIBUTES_UPTODATE_TRUE = ImmutableNodes
70             .leafNode(QName.create(Attributes.QNAME, "uptodate"), Boolean.TRUE);
71
72     private final Map<PathArgument, RouteEntry> routeEntries = new HashMap<>();
73     private final YangInstanceIdentifier locRibTarget;
74     private final NodeIdentifierWithPredicates tableKey;
75     private final ExportPolicyPeerTracker exportPolicyPeerTracker;
76     private final NodeIdentifier attributesIdentifier;
77     private final Long ourAs;
78     private final RIBSupport ribSupport;
79     private final TablesKey localTablesKey;
80     private final YangInstanceIdentifier target;
81     private final DOMDataTreeChangeService service;
82     private final PathSelectionMode pathSelectionMode;
83     private final LongAdder totalPathsCounter = new LongAdder();
84     private final LongAdder totalPrefixesCounter = new LongAdder();
85     private DOMTransactionChain chain;
86     @GuardedBy("this")
87     private ListenerRegistration<LocRibWriter> reg;
88
89     private LocRibWriter(final RIBSupportContextRegistry registry, final DOMTransactionChain chain,
90         final YangInstanceIdentifier target, final Long ourAs, final DOMDataTreeChangeService service,
91         final ExportPolicyPeerTracker exportPolicyPeerTracker, final TablesKey tablesKey,
92         final PathSelectionMode pathSelectionMode) {
93         this.chain = requireNonNull(chain);
94         this.target = requireNonNull(target);
95         this.tableKey  = RibSupportUtils.toYangTablesKey(tablesKey);
96         this.localTablesKey = tablesKey;
97         this.locRibTarget = YangInstanceIdentifier.create(target.node(LocRib.QNAME).node(Tables.QNAME)
98             .node(this.tableKey).getPathArguments());
99         this.ourAs = requireNonNull(ourAs);
100         this.service = requireNonNull(service);
101         this.ribSupport = registry.getRIBSupportContext(tablesKey).getRibSupport();
102         this.attributesIdentifier = this.ribSupport.routeAttributesIdentifier();
103         this.exportPolicyPeerTracker = exportPolicyPeerTracker;
104         this.pathSelectionMode = pathSelectionMode;
105
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 PathArgument 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,
203         final DOMDataWriteTransaction tx) {
204         if (!table.getDataBefore().isPresent() && this.exportPolicyPeerTracker.isTableSupported(peerIdOfNewPeer)) {
205             this.exportPolicyPeerTracker.registerPeerAsInitialized(peerIdOfNewPeer);
206             LOG.debug("Peer {} table has been created, inserting existent routes", peerIdOfNewPeer);
207             final PeerRole newPeerRole = this.exportPolicyPeerTracker.getRole(IdentifierUtils.peerPath(rootPath));
208             final PeerExportGroup peerGroup = this.exportPolicyPeerTracker.getPeerGroup(newPeerRole);
209             this.routeEntries.forEach((key, value) -> value.writeRoute(peerIdOfNewPeer, key,
210                 rootPath.getParent().getParent().getParent(), peerGroup, this.localTablesKey,
211                 this.exportPolicyPeerTracker, this.ribSupport, tx));
212         }
213     }
214
215     private void updateNodes(final DataTreeCandidateNode table, final PeerId peerId, final DOMDataWriteTransaction tx,
216         final Map<RouteUpdateKey, RouteEntry> routes) {
217         for (final DataTreeCandidateNode child : table.getChildNodes()) {
218             LOG.debug("Modification type {}", child.getModificationType());
219             if (Attributes.QNAME.equals(child.getIdentifier().getNodeType())) {
220                 if (child.getDataAfter().isPresent()) {
221                     // putting uptodate attribute in
222                     LOG.trace("Uptodate found for {}", child.getDataAfter());
223                     tx.put(LogicalDatastoreType.OPERATIONAL, this.locRibTarget.node(child.getIdentifier()),
224                             child.getDataAfter().get());
225                 }
226                 continue;
227             }
228             updateRoutesEntries(child, peerId, routes);
229         }
230     }
231
232     private void updateRoutesEntries(final DataTreeCandidateNode child, final PeerId peerId,
233             final Map<RouteUpdateKey, RouteEntry> routes) {
234         final UnsignedInteger routerId = RouterIds.routerIdForPeerId(peerId);
235         final Collection<DataTreeCandidateNode> modifiedRoutes = this.ribSupport.changedRoutes(child);
236         for (final DataTreeCandidateNode route : modifiedRoutes) {
237             final NodeIdentifierWithPredicates routeId = (NodeIdentifierWithPredicates)
238                     this.ribSupport.createRouteKeyPathArgument(route.getIdentifier());
239             RouteEntry entry = this.routeEntries.get(routeId);
240             final Optional<NormalizedNode<?, ?>> maybeData = route.getDataAfter();
241             final Optional<NormalizedNode<?, ?>> maybeDataBefore = route.getDataBefore();
242             if (maybeData.isPresent()) {
243                 if (entry == null) {
244                     entry = createEntry(routeId);
245                 }
246                 entry.addRoute(routerId, this.ribSupport.extractPathId(maybeData.get()),
247                         this.attributesIdentifier, maybeData.get());
248                 this.totalPathsCounter.increment();
249             } else if (entry != null) {
250                 this.totalPathsCounter.decrement();
251                 if(entry.removeRoute(routerId, this.ribSupport.extractPathId(maybeDataBefore.get()))) {
252                     this.routeEntries.remove(routeId);
253                     this.totalPrefixesCounter.decrement();
254                     LOG.trace("Removed route from {}", routerId);
255                 }
256             }
257             final RouteUpdateKey routeUpdateKey = new RouteUpdateKey(peerId, routeId);
258             LOG.debug("Updated route {} entry {}", routeId, entry);
259             routes.put(routeUpdateKey, entry);
260         }
261     }
262
263     private void walkThrough(final DOMDataWriteTransaction tx,
264             final Set<Map.Entry<RouteUpdateKey, RouteEntry>> toUpdate) {
265         for (final Map.Entry<RouteUpdateKey, RouteEntry> e : toUpdate) {
266             LOG.trace("Walking through {}", e);
267             final RouteEntry entry = e.getValue();
268
269             if (!entry.selectBest(this.ourAs)) {
270                 LOG.trace("Best path has not changed, continuing");
271                 continue;
272             }
273             entry.updateRoute(this.localTablesKey, this.exportPolicyPeerTracker, this.locRibTarget,
274                     this.ribSupport, tx, e.getKey().getRouteId());
275         }
276     }
277
278     @Override
279     public long getPrefixesCount() {
280         return this.totalPrefixesCounter.longValue();
281     }
282
283     @Override
284     public long getPathsCount() {
285         return this.totalPathsCounter.longValue();
286     }
287 }