2 * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.protocol.bgp.rib.impl;
10 import com.google.common.annotations.VisibleForTesting;
11 import com.google.common.base.Optional;
12 import com.google.common.base.Preconditions;
13 import com.google.common.primitives.UnsignedInteger;
14 import java.util.Collection;
15 import java.util.HashMap;
17 import java.util.Map.Entry;
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.DOMDataTreeChangeListener;
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.rib.impl.spi.CacheDisconnectedPeers;
27 import org.opendaylight.protocol.bgp.rib.impl.spi.RIBSupportContextRegistry;
28 import org.opendaylight.protocol.bgp.rib.spi.RIBSupport;
29 import org.opendaylight.protocol.bgp.rib.spi.RibSupportUtils;
30 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.AsNumber;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.PeerId;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.PeerRole;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.bgp.rib.rib.LocRib;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.bgp.rib.rib.Peer;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.bgp.rib.rib.peer.AdjRibOut;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.bgp.rib.rib.peer.EffectiveRibIn;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.bgp.rib.rib.peer.SupportedTables;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.rib.Tables;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.rib.TablesKey;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.rib.tables.Attributes;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.rib.tables.Routes;
42 import org.opendaylight.yangtools.concepts.ListenerRegistration;
43 import org.opendaylight.yangtools.yang.common.QName;
44 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
45 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
46 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
47 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
48 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
49 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
50 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
51 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
52 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidateNode;
53 import org.opendaylight.yangtools.yang.data.api.schema.tree.ModificationType;
54 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
55 import org.slf4j.Logger;
56 import org.slf4j.LoggerFactory;
59 final class LocRibWriter implements AutoCloseable, DOMDataTreeChangeListener {
61 private static final Logger LOG = LoggerFactory.getLogger(LocRibWriter.class);
63 private static final LeafNode<Boolean> ATTRIBUTES_UPTODATE_TRUE = ImmutableNodes.leafNode(QName.create(Attributes.QNAME, "uptodate"), Boolean.TRUE);
64 private static final NodeIdentifier ROUTES_IDENTIFIER = new NodeIdentifier(Routes.QNAME);
65 private static final NodeIdentifier EFFRIBIN_NID = new NodeIdentifier(EffectiveRibIn.QNAME);
66 private static final NodeIdentifier TABLES_NID = new NodeIdentifier(Tables.QNAME);
68 private final Map<PathArgument, AbstractRouteEntry> routeEntries = new HashMap<>();
69 private final YangInstanceIdentifier locRibTarget;
70 private final DOMTransactionChain chain;
71 private final ExportPolicyPeerTracker peerPolicyTracker;
72 private final NodeIdentifier attributesIdentifier;
73 private final Long ourAs;
74 private final RIBSupport ribSupport;
75 private final NodeIdentifierWithPredicates tableKey;
76 private final TablesKey localTablesKey;
77 private final RIBSupportContextRegistry registry;
78 private final ListenerRegistration<LocRibWriter> reg;
79 private final CacheDisconnectedPeers cacheDisconnectedPeers;
81 private LocRibWriter(final RIBSupportContextRegistry registry, final DOMTransactionChain chain, final YangInstanceIdentifier target, final Long ourAs,
82 final DOMDataTreeChangeService service, final PolicyDatabase pd, final TablesKey tablesKey, final CacheDisconnectedPeers cacheDisconnectedPeers) {
83 this.chain = Preconditions.checkNotNull(chain);
84 this.tableKey = RibSupportUtils.toYangTablesKey(tablesKey);
85 this.localTablesKey = tablesKey;
86 this.locRibTarget = YangInstanceIdentifier.create(target.node(LocRib.QNAME).node(Tables.QNAME).node(this.tableKey).getPathArguments());
87 this.ourAs = Preconditions.checkNotNull(ourAs);
88 this.registry = registry;
89 this.ribSupport = this.registry.getRIBSupportContext(tablesKey).getRibSupport();
90 this.attributesIdentifier = this.ribSupport.routeAttributesIdentifier();
91 this.peerPolicyTracker = new ExportPolicyPeerTracker(pd);
92 this.cacheDisconnectedPeers = cacheDisconnectedPeers;
94 final DOMDataWriteTransaction tx = this.chain.newWriteOnlyTransaction();
95 tx.merge(LogicalDatastoreType.OPERATIONAL, this.locRibTarget.node(Routes.QNAME), this.ribSupport.emptyRoutes());
96 tx.merge(LogicalDatastoreType.OPERATIONAL, this.locRibTarget.node(Attributes.QNAME).node(ATTRIBUTES_UPTODATE_TRUE.getNodeType()), ATTRIBUTES_UPTODATE_TRUE);
99 final YangInstanceIdentifier tableId = target.node(Peer.QNAME).node(Peer.QNAME);
101 this.reg = service.registerDataTreeChangeListener(new DOMDataTreeIdentifier(LogicalDatastoreType.OPERATIONAL, tableId), this);
104 public static LocRibWriter create(@Nonnull final RIBSupportContextRegistry registry, @Nonnull final TablesKey tablesKey, @Nonnull final DOMTransactionChain chain, @Nonnull final YangInstanceIdentifier target,
105 @Nonnull final AsNumber ourAs, @Nonnull final DOMDataTreeChangeService service, @Nonnull final PolicyDatabase pd, final CacheDisconnectedPeers cacheDisconnectedPeers) {
106 return new LocRibWriter(registry, chain, target, ourAs.getValue(), service, pd, tablesKey, cacheDisconnectedPeers);
110 public void close() {
112 // FIXME: wait for the chain to close? unfortunately RIBImpl is the listener, so that may require some work
117 private AbstractRouteEntry createEntry(final PathArgument routeId) {
118 final AbstractRouteEntry ret = this.ribSupport.isComplexRoute() ? new ComplexRouteEntry() : new SimpleRouteEntry();
119 this.routeEntries.put(routeId, ret);
120 LOG.trace("Created new entry for {}", routeId);
125 public void onDataTreeChanged(final Collection<DataTreeCandidate> changes) {
126 LOG.trace("Received data change {} to LocRib {}", changes, this);
128 final DOMDataWriteTransaction tx = this.chain.newWriteOnlyTransaction();
131 * We use two-stage processing here in hopes that we avoid duplicate
132 * calculations when multiple peers have changed a particular entry.
134 final Map<RouteUpdateKey, AbstractRouteEntry> toUpdate = update(tx, changes);
136 // Now walk all updated entries
137 walkThrough(tx, toUpdate);
138 } catch (final Exception e) {
139 LOG.error("Failed to completely propagate updates {}, state is undefined", changes, e);
145 private Map<RouteUpdateKey, AbstractRouteEntry> update(final DOMDataWriteTransaction tx, final Collection<DataTreeCandidate> changes) {
146 final Map<RouteUpdateKey, AbstractRouteEntry> ret = new HashMap<>();
148 for (final DataTreeCandidate tc : changes) {
149 final YangInstanceIdentifier rootPath = tc.getRootPath();
150 final DataTreeCandidateNode rootNode = tc.getRootNode();
151 final NodeIdentifierWithPredicates peerKey = IdentifierUtils.peerKey(rootPath);
152 final PeerId peerId = IdentifierUtils.peerId(peerKey);
153 filterOutPeerRole(peerId, rootNode, rootPath);
154 filterOutChangesToSupportedTables(peerId, rootNode);
155 filterOutAnyChangeOutsideEffRibsIn(peerId, rootNode, ret, rootPath, tx);
161 private void filterOutAnyChangeOutsideEffRibsIn(final PeerId peerId, final DataTreeCandidateNode rootNode,
162 final Map<RouteUpdateKey, AbstractRouteEntry> ret, final YangInstanceIdentifier rootPath, final DOMDataWriteTransaction tx) {
163 final DataTreeCandidateNode ribIn = rootNode.getModifiedChild(EFFRIBIN_NID);
165 LOG.trace("Skipping change {}", rootNode.getIdentifier());
168 final DataTreeCandidateNode table = ribIn.getModifiedChild(TABLES_NID).getModifiedChild(this.tableKey);
170 LOG.trace("Skipping change {}", rootNode.getIdentifier());
173 initializeTableWithExistenRoutes(table, peerId, rootPath, tx);
174 updateNodes(table, peerId, tx, ret);
177 private void filterOutChangesToSupportedTables(final PeerId peerIdOfNewPeer, final DataTreeCandidateNode rootNode) {
178 final DataTreeCandidateNode tablesChange = rootNode.getModifiedChild(AbstractPeerRoleTracker.PEER_TABLES);
179 if (tablesChange != null) {
180 final NodeIdentifierWithPredicates supTablesKey = RibSupportUtils.toYangKey(SupportedTables.QNAME, this.localTablesKey);
181 final DataTreeCandidateNode containsLocalKeyTable = tablesChange.getModifiedChild(supTablesKey);
182 if(containsLocalKeyTable != null) {
183 this.peerPolicyTracker.onTablesChanged(peerIdOfNewPeer, containsLocalKeyTable);
188 private void initializeTableWithExistenRoutes(final DataTreeCandidateNode table, final PeerId peerIdOfNewPeer, final YangInstanceIdentifier rootPath,
189 final DOMDataWriteTransaction tx) {
190 if (!table.getDataBefore().isPresent() && isTableSupported(peerIdOfNewPeer)) {
191 LOG.debug("Peer {} table has been created, inserting existent routes", peerIdOfNewPeer);
192 final PeerRole newPeerRole = this.peerPolicyTracker.getRole(IdentifierUtils.peerPath(rootPath));
193 final PeerExportGroup peerGroup = this.peerPolicyTracker.getPeerGroup(newPeerRole);
194 for (Map.Entry<PathArgument, AbstractRouteEntry> entry : this.routeEntries.entrySet()) {
195 final AbstractRouteEntry routeEntry = entry.getValue();
196 final PathArgument routeId = entry.getKey();
197 final YangInstanceIdentifier routeTarget = getRouteTarget(rootPath, routeId);
198 final NormalizedNode<?, ?> value = routeEntry.createValue(routeId);
199 final PeerId routePeerId = RouterIds.createPeerId(routeEntry.getBestRouterId());
200 final ContainerNode effectiveAttributes = peerGroup.effectiveAttributes(routePeerId, routeEntry.attributes());
201 if (effectiveAttributes != null && value != null) {
202 LOG.debug("Write route {} to peer AdjRibsOut {}", value, peerIdOfNewPeer);
203 tx.put(LogicalDatastoreType.OPERATIONAL, routeTarget, value);
204 tx.put(LogicalDatastoreType.OPERATIONAL, routeTarget.node(this.attributesIdentifier), effectiveAttributes);
210 private YangInstanceIdentifier getRouteTarget(final YangInstanceIdentifier rootPath, final PathArgument routeId) {
211 return this.ribSupport.routePath(rootPath.node(AdjRibOut.QNAME).node(Tables.QNAME).node(this.tableKey).node(ROUTES_IDENTIFIER), routeId);
214 private void filterOutPeerRole(final PeerId peerId, final DataTreeCandidateNode rootNode, final YangInstanceIdentifier rootPath) {
215 final DataTreeCandidateNode roleChange = rootNode.getModifiedChild(AbstractPeerRoleTracker.PEER_ROLE_NID);
216 if (roleChange != null) {
217 if (!rootNode.getModificationType().equals(ModificationType.DELETE)) {
218 this.cacheDisconnectedPeers.reconnected(peerId);
220 this.peerPolicyTracker.onDataTreeChanged(roleChange, IdentifierUtils.peerPath(rootPath));
224 private void updateNodes(final DataTreeCandidateNode table, final PeerId peerId, final DOMDataWriteTransaction tx,
225 final Map<RouteUpdateKey, AbstractRouteEntry> routes) {
226 for (final DataTreeCandidateNode child : table.getChildNodes()) {
227 LOG.debug("Modification type {}", child.getModificationType());
228 if ((Attributes.QNAME).equals(child.getIdentifier().getNodeType())) {
229 if (child.getDataAfter().isPresent()) {
230 // putting uptodate attribute in
231 LOG.trace("Uptodate found for {}", child.getDataAfter());
232 tx.put(LogicalDatastoreType.OPERATIONAL, this.locRibTarget.node(child.getIdentifier()), child.getDataAfter().get());
236 updateRoutesEntries(child, peerId, routes);
240 private void updateRoutesEntries(final DataTreeCandidateNode child, final PeerId peerId, final Map<RouteUpdateKey, AbstractRouteEntry> routes) {
241 final UnsignedInteger routerId = RouterIds.routerIdForPeerId(peerId);
242 final Collection<DataTreeCandidateNode> modifiedRoutes = this.ribSupport.changedRoutes(child);
243 for (final DataTreeCandidateNode route : modifiedRoutes) {
244 final PathArgument routeId = route.getIdentifier();
245 AbstractRouteEntry entry = this.routeEntries.get(routeId);
247 final Optional<NormalizedNode<?, ?>> maybeData = route.getDataAfter();
248 final RouteUpdateKey routeUpdateKey = new RouteUpdateKey(peerId, routeId);
249 if (maybeData.isPresent()) {
251 entry = createEntry(routeId);
253 entry.addRoute(routerId, this.attributesIdentifier, maybeData.get());
254 } else if (entry != null && entry.removeRoute(routerId)) {
255 this.routeEntries.remove(routeId);
256 LOG.trace("Removed route from {}", routerId);
259 LOG.debug("Updated route {} entry {}", routeId, entry);
260 routes.put(routeUpdateKey, entry);
264 private void walkThrough(final DOMDataWriteTransaction tx, final Map<RouteUpdateKey, AbstractRouteEntry> toUpdate) {
265 for (final Entry<RouteUpdateKey, AbstractRouteEntry> e : toUpdate.entrySet()) {
266 LOG.trace("Walking through {}", e);
267 final AbstractRouteEntry entry = e.getValue();
268 final RouteUpdateKey key = e.getKey();
269 final NormalizedNode<?, ?> value;
270 final PathArgument routeId = key.getRouteId();
271 PeerId routePeerId = key.getPeerId();
273 if (!entry.selectBest(this.ourAs)) {
274 // Best path has not changed, no need to do anything else. Proceed to next route.
275 LOG.trace("Continuing");
278 routePeerId = RouterIds.createPeerId(entry.getBestRouterId());
279 value = entry.createValue(routeId);
280 LOG.trace("Selected best value {}", value);
284 fillLocRib(tx, entry, value, routeId);
285 fillAdjRibsOut(tx, entry, value, routeId, routePeerId);
289 private void fillLocRib(final DOMDataWriteTransaction tx, final AbstractRouteEntry entry, final NormalizedNode<?, ?> value, final PathArgument routeId) {
290 final YangInstanceIdentifier writePath = this.ribSupport.routePath(this.locRibTarget.node(ROUTES_IDENTIFIER), routeId);
292 LOG.debug("Write route to LocRib {}", value);
293 tx.put(LogicalDatastoreType.OPERATIONAL, writePath, value);
295 LOG.debug("Delete route from LocRib {}", entry);
296 tx.delete(LogicalDatastoreType.OPERATIONAL, writePath);
301 private void fillAdjRibsOut(final DOMDataWriteTransaction tx, final AbstractRouteEntry entry, final NormalizedNode<?, ?> value,
302 final PathArgument routeId, final PeerId routePeerId) {
304 * We need to keep track of routers and populate adj-ribs-out, too. If we do not, we need to
305 * expose from which client a particular route was learned from in the local RIB, and have
306 * the listener perform filtering.
308 * We walk the policy set in order to minimize the amount of work we do for multiple peers:
309 * if we have two eBGP peers, for example, there is no reason why we should perform the translation
312 final ContainerNode attributes = entry == null ? null : entry.attributes();
313 for (final PeerRole role : PeerRole.values()) {
314 final PeerExportGroup peerGroup = this.peerPolicyTracker.getPeerGroup(role);
315 if (peerGroup != null) {
316 final ContainerNode effectiveAttributes = peerGroup.effectiveAttributes(routePeerId, attributes);
317 for (final Entry<PeerId, YangInstanceIdentifier> pid : peerGroup.getPeers()) {
318 final PeerId peerDestiny = pid.getKey();
319 if (!routePeerId.equals(peerDestiny) && isTableSupported(peerDestiny) && !this.cacheDisconnectedPeers.isPeerDisconnected(peerDestiny)) {
320 final YangInstanceIdentifier routeTarget = getRouteTarget(pid.getValue(), routeId);
321 if (value != null && effectiveAttributes != null) {
322 LOG.debug("Write route {} to peers AdjRibsOut {}", value, peerDestiny);
323 tx.put(LogicalDatastoreType.OPERATIONAL, routeTarget, value);
324 tx.put(LogicalDatastoreType.OPERATIONAL, routeTarget.node(this.attributesIdentifier), effectiveAttributes);
326 LOG.trace("Removing {} from transaction for peer {}", routeTarget, peerDestiny);
327 tx.delete(LogicalDatastoreType.OPERATIONAL, routeTarget);
335 private boolean isTableSupported(final PeerId key) {
336 if (!this.peerPolicyTracker.isTableSupported(key, this.localTablesKey)) {
337 LOG.trace("Route rejected, peer {} does not support this table type {}", key, this.localTablesKey);