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