7ba5acc7b1b9abf44b419b79e6f75bbd61f96a9e
[bgpcep.git] / bgp / path-selection-mode / src / main / java / org / opendaylight / protocol / bgp / mode / impl / base / BaseAbstractRouteEntry.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.mode.impl.base;
9
10 import com.google.common.annotations.VisibleForTesting;
11 import com.google.common.primitives.UnsignedInteger;
12 import java.util.Map;
13 import java.util.Optional;
14 import javax.annotation.concurrent.NotThreadSafe;
15 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
16 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
17 import org.opendaylight.protocol.bgp.mode.api.BestPath;
18 import org.opendaylight.protocol.bgp.mode.api.RouteEntry;
19 import org.opendaylight.protocol.bgp.rib.spi.CacheDisconnectedPeers;
20 import org.opendaylight.protocol.bgp.rib.spi.ExportPolicyPeerTracker;
21 import org.opendaylight.protocol.bgp.rib.spi.PeerExportGroup;
22 import org.opendaylight.protocol.bgp.rib.spi.RIBSupport;
23 import org.opendaylight.protocol.bgp.rib.spi.RibSupportUtils;
24 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.PeerId;
25 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.PeerRole;
26 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.bgp.rib.rib.peer.AdjRibOut;
27 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.rib.Tables;
28 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.rib.TablesKey;
29 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.bgp.rib.rev130925.rib.tables.Routes;
30 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
31 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
32 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
33 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
34 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
35 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38
39 @NotThreadSafe
40 abstract class BaseAbstractRouteEntry implements RouteEntry {
41
42     private static final NodeIdentifier ROUTES_IDENTIFIER = new NodeIdentifier(Routes.QNAME);
43     private static final Logger LOG = LoggerFactory.getLogger(BaseAbstractRouteEntry.class);
44     private static final ContainerNode[] EMPTY_ATTRIBUTES = new ContainerNode[0];
45     private OffsetMap offsets = OffsetMap.EMPTY;
46     private ContainerNode[] values = EMPTY_ATTRIBUTES;
47     private Optional<BaseBestPath> bestPath = Optional.empty();
48     private Optional<BaseBestPath> removedBestPath = Optional.empty();
49
50     private int addRoute(final UnsignedInteger routerId, final ContainerNode attributes) {
51         int offset = this.offsets.offsetOf(routerId);
52         if (offset < 0) {
53             final OffsetMap newOffsets = this.offsets.with(routerId);
54             offset = newOffsets.offsetOf(routerId);
55
56             this.values = newOffsets.expand(this.offsets, this.values, offset);
57             this.offsets = newOffsets;
58         }
59
60         this.offsets.setValue(this.values, offset, attributes);
61         LOG.trace("Added route from {} attributes {}", routerId, attributes);
62         return offset;
63     }
64
65     /**
66      * Remove route
67      *
68      * @param routerId router ID in unsigned integer
69      * @param offset offset Offset of removed route
70      * @return true if its the last route
71      */
72     final boolean removeRoute(final UnsignedInteger routerId, final int offset) {
73         this.values = this.offsets.removeValue(this.values, offset);
74         this.offsets = this.offsets.without(routerId);
75         return this.offsets.isEmty();
76     }
77
78     @Override
79     public final boolean selectBest(final long localAs) {
80         /*
81          * FIXME: optimize flaps by making sure we consider stability of currently-selected route.
82          */
83         final BasePathSelector selector = new BasePathSelector(localAs);
84
85         // Select the best route.
86         for (int i = 0; i < this.offsets.size(); ++i) {
87             final UnsignedInteger routerId = this.offsets.getRouterId(i);
88             final ContainerNode attributes = this.offsets.getValue(this.values, i);
89             LOG.trace("Processing router id {} attributes {}", routerId, attributes);
90             selector.processPath(routerId, attributes);
91         }
92
93         // Get the newly-selected best path.
94         final Optional<BaseBestPath> newBestPath = Optional.ofNullable(selector.result());
95         final boolean modified = !newBestPath.equals(this.bestPath);
96         if (modified) {
97             this.removedBestPath = this.bestPath;
98         }
99
100         LOG.trace("Previous best {}, current best {}, result {}", this.bestPath, newBestPath, modified);
101         this.bestPath = newBestPath;
102         return modified;
103     }
104
105     @Override
106     public int addRoute(final UnsignedInteger routerId, final NodeIdentifier attrII, final NormalizedNode<?, ?> data) {
107         LOG.trace("Find {} in {}", attrII, data);
108         final ContainerNode advertisedAttrs = (ContainerNode) NormalizedNodes.findNode(data, attrII).orNull();
109         return addRoute(routerId, advertisedAttrs);
110     }
111
112     @Override
113     public void updateRoute(final TablesKey localTK, final ExportPolicyPeerTracker peerPT, final YangInstanceIdentifier locRibTarget, final RIBSupport ribSup,
114         final CacheDisconnectedPeers discPeers, final DOMDataWriteTransaction tx, final PathArgument routeIdPA) {
115         final YangInstanceIdentifier writePath = ribSup.routePath(locRibTarget.node(ROUTES_IDENTIFIER), routeIdPA);
116         if (this.removedBestPath.isPresent()) {
117             removePathFromDataStore(this.removedBestPath.get(), routeIdPA, writePath, peerPT, localTK, ribSup, discPeers, tx);
118             this.removedBestPath = Optional.empty();
119         }
120         if (this.bestPath.isPresent()) {
121             addPathToDataStore(this.bestPath.get(), routeIdPA, writePath, ribSup, peerPT, localTK, discPeers, tx);
122         }
123     }
124
125     @Override
126     public void writeRoute(final PeerId destPeer, final PathArgument routeId, final YangInstanceIdentifier rootPath,
127         final PeerExportGroup peerGroup, final TablesKey localTK, final RIBSupport ribSup, final DOMDataWriteTransaction tx) {
128         final BaseBestPath path = this.bestPath.get();
129         final ContainerNode effAttrib = peerGroup.effectiveAttributes(path.getPeerId(), path.getAttributes());
130         writeRoute(destPeer, getRouteTarget(ribSup, rootPath, routeId, localTK), effAttrib, createValue(routeId, path), ribSup, tx);
131     }
132
133     private void removePathFromDataStore(final BestPath path, final PathArgument routeIdPathArgument, final YangInstanceIdentifier writePath,
134         final ExportPolicyPeerTracker peerPT, final TablesKey localTK, final RIBSupport ribSup, final CacheDisconnectedPeers discPeers,
135         final DOMDataWriteTransaction tx) {
136         LOG.trace("Best Path removed {}", path);
137         fillLocRib(writePath, tx, null);
138         fillAdjRibsOut(tx, null, null, routeIdPathArgument, path.getPeerId(), peerPT, localTK, ribSup, discPeers);
139     }
140
141     private void addPathToDataStore(final BestPath path, final PathArgument routeIdPathArgument, final YangInstanceIdentifier writePath,
142         final RIBSupport ribSup, final ExportPolicyPeerTracker peerPT, final TablesKey localTK, final CacheDisconnectedPeers discPeers,
143         final DOMDataWriteTransaction tx) {
144         final NormalizedNode<?, ?> value = createValue(routeIdPathArgument, path);
145         LOG.trace("Selected best value {}", value);
146         fillLocRib(writePath, tx, value);
147         fillAdjRibsOut(tx, path.getAttributes(), value, routeIdPathArgument, path.getPeerId(), peerPT, localTK, ribSup, discPeers);
148     }
149
150     private boolean writeRoute(final PeerId destPeer, final YangInstanceIdentifier routeTarget, final ContainerNode effAttrib,
151         final NormalizedNode<?, ?> value, final RIBSupport ribSup, final DOMDataWriteTransaction tx) {
152         if (effAttrib != null && value != null) {
153             LOG.debug("Write route {} to peer AdjRibsOut {}", value, destPeer);
154             tx.put(LogicalDatastoreType.OPERATIONAL, routeTarget, value);
155             tx.put(LogicalDatastoreType.OPERATIONAL, routeTarget.node(ribSup.routeAttributesIdentifier()), effAttrib);
156             return true;
157         }
158         return false;
159     }
160
161     final OffsetMap getOffsets() {
162         return this.offsets;
163     }
164
165     private void fillLocRib(final YangInstanceIdentifier writePath, final DOMDataWriteTransaction tx, final NormalizedNode<?, ?> value) {
166         if (value != null) {
167             LOG.debug("Write route to LocRib {}", value);
168             tx.put(LogicalDatastoreType.OPERATIONAL, writePath, value);
169         } else {
170             LOG.debug("Delete route from LocRib {}", writePath);
171             tx.delete(LogicalDatastoreType.OPERATIONAL, writePath);
172         }
173     }
174
175     @VisibleForTesting
176     private void fillAdjRibsOut(final DOMDataWriteTransaction tx, final ContainerNode attributes, final NormalizedNode<?, ?> value,
177         final PathArgument routeId, final PeerId routePeerId, final ExportPolicyPeerTracker peerPT, final TablesKey localTK, final RIBSupport ribSup,
178         final CacheDisconnectedPeers discPeers) {
179         /*
180          * We need to keep track of routers and populate adj-ribs-out, too. If we do not, we need to
181          * expose from which client a particular route was learned from in the local RIB, and have
182          * the listener perform filtering.
183          *
184          * We walk the policy set in order to minimize the amount of work we do for multiple peers:
185          * if we have two eBGP peers, for example, there is no reason why we should perform the translation
186          * multiple times.
187          */
188         for (final PeerRole role : PeerRole.values()) {
189             if (PeerRole.Internal.equals(role)) {
190                 continue;
191             }
192             final PeerExportGroup peerGroup = peerPT.getPeerGroup(role);
193             if (peerGroup != null) {
194                 final ContainerNode effAttrib = peerGroup.effectiveAttributes(routePeerId, attributes);
195                 peerGroup.getPeers().stream()
196                     .filter(pid -> filterRoutes(pid, routePeerId, peerPT, localTK, discPeers))
197                     .forEach(pid -> update(pid.getKey(), getRouteTarget(ribSup, pid.getValue(), routeId, localTK), effAttrib, value,
198                         ribSup, tx));
199             }
200         }
201     }
202
203     private boolean filterRoutes(final Map.Entry<PeerId, YangInstanceIdentifier> pid, final PeerId destPeer, final ExportPolicyPeerTracker peerPT,
204         final TablesKey localTK, final CacheDisconnectedPeers discPeers) {
205         final PeerId peerId = pid.getKey();
206         return !peerId.equals(destPeer) && isTableSupported(destPeer, peerPT, localTK) && !discPeers.isPeerDisconnected(destPeer);
207     }
208
209     private void update(final PeerId destPeer, final YangInstanceIdentifier routeTarget, final ContainerNode effAttrib,
210         final NormalizedNode<?, ?> value, final RIBSupport ribSup, final DOMDataWriteTransaction tx) {
211         if (!writeRoute(destPeer, routeTarget, effAttrib, value, ribSup, tx)) {
212             LOG.trace("Removing {} from transaction for peer {}", routeTarget, destPeer);
213             tx.delete(LogicalDatastoreType.OPERATIONAL, routeTarget);
214         }
215     }
216
217     private boolean isTableSupported(final PeerId destPeer, final ExportPolicyPeerTracker peerPT, final TablesKey localTK) {
218         if (!peerPT.isTableSupported(destPeer)) {
219             LOG.trace("Route rejected, peer {} does not support this table type {}", destPeer, localTK);
220             return false;
221         }
222         return true;
223     }
224
225
226     private YangInstanceIdentifier getRouteTarget(final RIBSupport ribSup, final YangInstanceIdentifier rootPath, final PathArgument routeId,
227         final TablesKey localTK) {
228         return ribSup.routePath(rootPath.node(AdjRibOut.QNAME).node(Tables.QNAME).node(RibSupportUtils.toYangTablesKey(localTK)).
229             node(ROUTES_IDENTIFIER), routeId);
230     }
231 }